Skip to content

auth: support OAuth2 session persistence - #1058

Merged
guglielmo-san merged 6 commits into
modelcontextprotocol:mainfrom
smlx:oauth2-session-save
Jul 13, 2026
Merged

guglielmo-san merged 6 commits into
modelcontextprotocol:mainfrom
smlx:oauth2-session-save

Conversation

@smlx

@smlx smlx commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

This change adds NewTokenSource and InitialTokenSource fields to AuthorizationCodeHandlerConfig. NewTokenSource enables wrapping the underlying token source to intercept token refreshes. InitialTokenSource enables configuring the token source used by the
AuthorizationCodeHandler on initialization. Together these features facilitate persisting OAuth2 sessions across restarts.

Includes a package example demonstrating the persistence pattern.

Fixes: #901.

See #901 for the previous discussion behind this design.

Comment thread auth/authorization_code.go Outdated
Comment thread auth/authorization_code.go Outdated
Comment thread auth/authorization_code.go Outdated
Comment thread auth/authorization_code.go Outdated
Comment thread auth/authorization_code.go
Comment thread auth/authorization_code.go Outdated
Comment thread auth/auth_example_test.go Outdated
@smlx
smlx force-pushed the oauth2-session-save branch from 7615caf to 13c5f7b Compare July 10, 2026 03:34
smlx added 2 commits July 10, 2026 11:44
This change adds NewTokenSource and InitialTokenSource fields to
AuthorizationCodeHandlerConfig. NewTokenSource enables wrapping the
underlying token source to intercept token refreshes. InitialTokenSource
enables configuring the token source used by the
AuthorizationCodeHandler on initialization. Together these features
facilitate persisting OAuth2 sessions across restarts.

Includes a package example demonstrating the persistence pattern.
Add a mutex to AuthorizationCodeHandler to prevent data races when
reading or updating the tokenSource and grantedScopes fields.
@smlx
smlx force-pushed the oauth2-session-save branch from 13c5f7b to 5c4846d Compare July 10, 2026 03:44
@smlx
smlx requested a review from jba July 10, 2026 03:45

@jba jba left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I approve, but an official maintainer needs to make the final call.

@jba
jba requested review from guglielmo-san and jba July 10, 2026 12:13
@guglielmo-san
guglielmo-san merged commit beb3d1a into modelcontextprotocol:main Jul 13, 2026
9 checks passed
@smlx
smlx deleted the oauth2-session-save branch July 13, 2026 13:55
guglielmo-san added a commit to luiacuaniello/go-sdk that referenced this pull request Oct 21, 2026
…handlers (modelcontextprotocol#1432)

Fixes modelcontextprotocol#1430.
Fixes modelcontextprotocol#1431.

Two fixes to how the `extauth` handlers manage their token source. They
change the same lines of `ClientCredentialsHandler.Authorize`, so they
come in one PR, one commit each.

**Token source bound to the `Authorize` context (modelcontextprotocol#1430)**

`ClientCredentialsHandler.Authorize` built its token source with
`clientcredentials.Config.TokenSource(ctx)`, where `ctx` is the context
passed to `Authorize`. `clientcredentials` keeps that context and uses
it for every later token request. The transport calls `Authorize` from a
request- or connect-scoped context and keeps using the token source for
the whole connection, so once that context was done and the first access
token expired, every request failed with `context canceled`.
`setMCPHeaders` only re-authorizes after an `invalid_grant`
`RetrieveError`, so the connection did not recover. modelcontextprotocol#988 fixed the same
problem in `AuthorizationCodeHandler`.

The first token is now requested with `ctx`, so that `Authorize` still
honors its context, and the token source used afterwards is built on a
background context that carries the configured HTTP client. The fake
authorization server now applies `Config.AccessTokenTTL` to the
client_credentials grant too, so tests can make its token expire.

**Unsynchronized token source (modelcontextprotocol#1431)**

`ClientCredentialsHandler` and `EnterpriseHandler` wrote their
`tokenSource` (and, for `ClientCredentialsHandler`, `grantedScopes`) in
`Authorize` and read it in `TokenSource` without synchronization. The
streamable client transport calls `Authorize` for any request that fails
with 401 or 403, and `TokenSource` before every request, so concurrent
requests race on that state. `AuthorizationCodeHandler` guards the same
fields with a mutex since modelcontextprotocol#1058. Both handlers now guard them with a
`sync.RWMutex`.

**Tests**

- `TestClientCredentialsHandler_TokenAfterContextCancel`: after the
context passed to `Authorize` is cancelled, the token source still gets
a new token once the first one has expired. Fails without the first
commit.
- `TestClientCredentialsHandler_ConcurrentAuthorize` and
`TestEnterpriseHandler_ConcurrentAuthorize`: run `Authorize` and
`TokenSource` from several goroutines. Fail under the race detector
without the second commit.

`go test ./...`, `go test -race` on `auth` and `mcp`, `go vet ./...`,
staticcheck v0.6.1 and the client conformance suite (0.2.0-alpha.11, run
as in CI with the baseline) pass.

---------

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: Client API support for saving and restoring OAuth2 sessions

3 participants