Skip to content

Commit 63888df

Browse files
committed
fix(auth): treat tokens as expired at exact expiration timestamp
1 parent 2af9e30 commit 63888df

3 files changed

Lines changed: 22 additions & 2 deletions

File tree

‎src/mcp/client/auth/extensions/identity_assertion.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ async def _auth_flow(self, request: httpx2.Request) -> AsyncGenerator[httpx2.Req
167167
self._expiry = calculate_token_expiry(self._tokens.expires_in) if self._tokens else None
168168
self._initialized = True
169169

170-
if self._tokens and (self._expiry is None or time.time() <= self._expiry):
170+
if self._tokens and (self._expiry is None or time.time() < self._expiry):
171171
request.headers["Authorization"] = f"Bearer {self._tokens.access_token}"
172172
response = yield request
173173

‎src/mcp/client/auth/oauth2.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def is_token_valid(self) -> bool:
188188
return bool(
189189
self.current_tokens
190190
and self.current_tokens.access_token
191-
and (not self.token_expiry_time or time.time() <= self.token_expiry_time)
191+
and (not self.token_expiry_time or time.time() < self.token_expiry_time)
192192
)
193193

194194
def can_refresh_token(self) -> bool:

‎tests/client/test_auth.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,26 @@ async def test_token_validity_checking(self, oauth_provider: OAuthClientProvider
244244
assert not context.is_token_valid()
245245
assert context.can_refresh_token() # Can still refresh
246246

247+
# Exactly at expiry boundary (current time equals expiry)
248+
now = time.time()
249+
context.token_expiry_time = now
250+
with mock.patch("time.time", return_value=now):
251+
assert not context.is_token_valid() # Token must not be accepted on or after expiry
252+
253+
# Token with expires_in=0 should be immediately invalid at issuance time
254+
zero_expiry_token = OAuthToken(
255+
access_token="zero_token",
256+
token_type="Bearer",
257+
expires_in=0,
258+
)
259+
context.current_tokens = zero_expiry_token
260+
with mock.patch("time.time", return_value=now):
261+
context.update_token_expiry(zero_expiry_token)
262+
assert not context.is_token_valid()
263+
264+
context.current_tokens = valid_tokens
265+
context.token_expiry_time = time.time() - 100
266+
247267
# Remove refresh token
248268
context.current_tokens.refresh_token = None
249269
assert not context.can_refresh_token()

0 commit comments

Comments
 (0)