Fix hourly MCP re-auth by correcting upstream expires_in - #16
Open
UsamaSadiq wants to merge 5 commits into
Open
Conversation
mpass-auth-proxy reports the browser session cookie lifetime as expires_in, but the Cognito token underneath still expires in an hour. FastMCP gates transparent refresh on that value, so the refresh never fired and clients were bounced through a full re-authorization every hour. Register an authlib compliance hook on both the authorization-code and refresh grants that derives expires_in from the access token's own exp.
Document the deliberate 60s floor window, drive the compliance hook through a real authlib client on both grants, and wrap an over-length test line.
Treat sub-second truncation drift as an already-truthful lifetime so a direct-to-Cognito response passes through untouched, document the deliberate 60s floor window, and correct the docstrings: renewal runs through the client's refresh grant, not transparent refresh, because the client-facing token now tracks the upstream hour. Cover the hook contract with a real authlib round-trip on both grants.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
MCP sessions were forced through a full interactive re-authorization every hour.
mpass-auth-proxyreports the browser session cookie lifetime asexpires_infrom its
/tokenendpoint, because oauth2-proxy is the session authority for thebrowser flow and trusts that value for its cookie. The Cognito access token
underneath still expires in one hour. The MCP servers point
COGNITO_UPSTREAM_TOKEN_URLat that same endpoint.On the MCP path the session authority is FastMCP
OAuthProxy, which keys renewaloff
expires_in: it storesexpires_at = now + expires_in. With the inflatedvalue that stored expiry sits days out, so at the one hour mark JWKS validation of
the real token fails while the refresh gate stays shut.
load_access_tokenreturns
None, the request 401s, and the client re-authorizes from scratch.This registers an authlib compliance hook on both the authorization code and the
refresh grant, deriving
expires_infrom the access token ownexpclaim so thestored lifetime matches reality and the session renews through the refresh grant
instead.
OAuthProxy._create_upstream_oauth_clientis the single factory for theauthlib client used by both grants, so one registration covers every path that
stores an upstream token lifetime.
Scope is confined to this repo.
/token,SESSION_COOKIE_MAX_AGE_SECONDS,oauth2-proxy and the Traefik router rules are untouched, so the browser flow
cannot be affected. The hook is inert where the server talks straight to Cognito,
which already reports a truthful lifetime.
Note that the client facing FastMCP token now also tracks the upstream hour and is
renewed by the client refresh token. Decoupling the two would require
fastmcp_access_token_expiry_seconds, which is absent from the fastmcp 3.2.x linethe sibling MCP servers pin, so it is deliberately not used here to keep all three
servers behaving identically.
Testing
baseline).
ruff checkclean on every changed file.tests/test_token_expiry.pycovers the correction itself, the pass throughcases (missing, empty, non JWT or undecodable access token, missing or non
numeric
exp, non 200, non JSON and non dict bodies, an already truthfullifetime), the 60 second floor, non numeric
expires_inrejection, and a roundtrip through a real
AsyncOAuth2Clientthat exercises the hook on both grants.confirm a tool call still succeeds past the one hour mark with no re-auth
prompt, and that
docker logs mpass-auth-proxyshowsgrant_type=refresh_tokenwith no warning following it.
this change makes that path run hourly where it previously never ran. Concurrent
or retried refreshes would surface as
invalid_grant.