feat(token): add RFC 8693 token-exchange grant for provider access tokens#2609
Open
spydon wants to merge 3 commits into
Open
feat(token): add RFC 8693 token-exchange grant for provider access tokens#2609spydon wants to merge 3 commits into
spydon wants to merge 3 commits into
Conversation
This was referenced Jul 2, 2026
This was referenced Jul 3, 2026
5dc0147 to
1d3f849
Compare
1d3f849 to
16f15c6
Compare
9b63794 to
f1c4695
Compare
f1c4695 to
0280c58
Compare
05f0a39 to
abaae77
Compare
abaae77 to
835be4e
Compare
cemalkilic
reviewed
Jul 14, 2026
…kens Adds grant_type=urn:ietf:params:oauth:grant-type:token-exchange so a client can sign in with a provider-issued access token (subject_token) instead of an OIDC id token. Facebook's native Android login only mints an OIDC id token (carrying the email claims) on the first authorization, so signup stays on the id_token grant. On subsequent logins Facebook returns only a classic access token with no profile claims. This grant verifies the token belongs to the app via a provider-specific AccessTokenVerifier (Facebook: /debug_token, checking app id, validity and user-token type), reads the provider subject from it, and signs in the existing identity. It does not create accounts. The provider is inferred from the subject_token_type rather than sent separately: a Facebook access token uses https://supabase.com/auth/token-type/facebook-access-token, so a new provider opts in by registering its own token type. Sign-ins are recorded in the audit log and metered under a dedicated token_exchange login type.
835be4e to
d97b05b
Compare
…d client ids Match the id_token grant, which accepts any configured Facebook client id as an acceptable audience. The token-exchange verifier previously checked debug_token's app_id against only the primary client id (ext.ClientID[0]), which rejected valid tokens issued for a secondary configured client id.
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.
What
Adds an RFC 8693 token-exchange grant to
POST /tokenso a client can sign in with a provider-issued access token instead of an OIDC id token:The provider is inferred from
subject_token_type, so the request carries no separateproviderfield.RFC: Token-exchange grant for provider access tokens
Why
Native Facebook login on Android (supabase/supabase-flutter#1287) only mints an OIDC id token (which carries
email/email_verified) on the first authorization. Every subsequent login returns a classic access token with no profile claims and no id token, so theid_tokengrant is unusable for the common repeat-login case. This is a Facebook platform limitation (Limited Login is iOS-only). See facebook/facebook-android-sdk#1132.The split, matching how Firebase and Auth0 handle Facebook:
id_tokengrant (has the email claims). This is where the account is created, including on Android.How
grant_typeis the standard RFC 8693 URN. The provider is inferred fromsubject_token_type: a Facebook access token useshttps://supabase.com/auth/token-type/facebook-access-token. New providers opt in by registering their own token type, so no new grant is needed and the request carries no separateproviderfield.provider.AccessTokenVerifierinterface. Facebook's implementation calls/debug_token(app token sent as a Bearer header, not in the URL; transport errors have the URL stripped so the token can't leak), and requiresis_valid, anapp_idmatching one of this project's configured Facebook client ids, and aUSERtoken type. It returns the token'suser_idas the subject.debug_tokenreturns no profile claims, so the grant looks up the existingauth.identitiesrow (provider,provider_id=user_id) and issues a session. It does not create accounts (a brand-new user signs up via the id-token first-login path), and rejects banned users and unconfirmed users (unlessAllowUnverifiedEmailSignInsis set), matching the other sign-in paths. A missing identity returns a genericInvalid subject_tokento avoid account enumeration; the client fallback for that edge case issignInWithOAuth(web).loginaudit log entry (with the provider trait) and are metered under a dedicatedtoken_exchangelogin type, since no OAuth authorization flow is involved.Tests
internal/api/token_exchange_test.go: sign-in of an existing identity (asserting the audit log entry), no-identity rejected, banned user rejected, unconfirmed user rejected, unconfirmed allowed whenAllowUnverifiedEmailSignInsis set, token from another app rejected, token issued for a secondary configured client id accepted, invalid token, non-user token, missing subject_token, missing subject_token_type, unsupported subject_token_type, disabled provider. All pass against Postgres.Notes / follow-ups
VerifyAccessTokenverifiesdebug_token'sapp_idagainst all configured Facebook client ids, matching theid_tokengrant, which already accepts any configured client id as an acceptable audience. The/debug_tokencall itself is authenticated with the primary client id.subject_token_typefor defense in depth once the client can produce one; the server flow is identical.