Skip to content

Fix/link identity provider token#2069

Open
samarth212 wants to merge 3 commits intosupabase:masterfrom
samarth212:fix/link-identity-provider-token
Open

Fix/link identity provider token#2069
samarth212 wants to merge 3 commits intosupabase:masterfrom
samarth212:fix/link-identity-provider-token

Conversation

@samarth212
Copy link
Copy Markdown

@samarth212 samarth212 commented Jan 28, 2026

fix(auth): allow provider tokens on linkIdentity callbacks

🔍 Description

Fixes linkIdentity callbacks that only include provider tokens so apps can access provider_token / provider_refresh_token after linking.

What changed?

  • Treat callbacks with only provider_token / provider_refresh_token as valid implicit OAuth callbacks.
  • Merge provider tokens into the existing stored session.
  • Add a browser test covering provider-token-only callbacks.

Why was this change needed?

OAuth identity linking can return only provider tokens on redirect. The client previously ignored those callbacks, making provider_token inaccessible and linkIdentity far less useful.

Closes #1676

📸 Screenshots/Examples

N/A

🔄 Breaking changes

  • This PR contains no breaking changes

📋 Checklist

  • I have read the Contributing Guidelines
  • My PR title follows the conventional commit format: ():
  • I have run npx nx format to ensure consistent code formatting
  • I have added tests for new functionality (if applicable)
  • I have updated documentation (if applicable)

📝 Additional notes

Tests run: npx nx test:auth auth-js
N/A (no docs changes required)

@samarth212 samarth212 requested review from a team as code owners January 28, 2026 04:03
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: d9a3bb2e-3cfa-4626-8557-535bd9792cf9

📥 Commits

Reviewing files that changed from the base of the PR and between cdb5220 and c30e189.

📒 Files selected for processing (2)
  • packages/core/auth-js/src/GoTrueClient.ts
  • packages/core/auth-js/test/GoTrueClient.browser.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/auth-js/test/GoTrueClient.browser.test.ts

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added support for OAuth implicit grant flows that use provider tokens to augment existing sessions
    • Enhanced callback URL detection to properly recognize provider token parameters in OAuth redirects
    • Improved session persistence by combining existing session data with provider tokens from callbacks

Walkthrough

Adds handling for OAuth redirect URLs that include only provider_token and provider_refresh_token. On detecting such an implicit-grant callback, the client loads the existing session via _loadSession/_useSession, merges the provider tokens into that session object, clears the URL fragment, and returns the augmented session along with redirect metadata.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant GoTrueClient
  participant SessionStore as _loadSession
  Browser->>GoTrueClient: Redirect URL with provider_token & provider_refresh_token
  GoTrueClient->>GoTrueClient: detect implicit grant via provider tokens
  GoTrueClient->>SessionStore: _loadSession()
  SessionStore-->>GoTrueClient: existing session (access_token present)
  GoTrueClient->>GoTrueClient: merge provider_token(s) into session
  GoTrueClient->>Browser: clear URL fragment
  GoTrueClient-->>Browser: return augmented session + redirectType
Loading

Assessment against linked issues

Objective (issue#) Addressed Explanation
Provide a way to obtain providerToken during linkIdentity flows (#1676)
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix/link identity provider token' is specific and directly related to the main change of handling provider tokens in identity linking flows.
Linked Issues check ✅ Passed The PR addresses issue #1676 by enabling provider tokens to be returned after linkIdentity callbacks, merging them into the stored session so applications can access provider_token and provider_refresh_token.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing provider token handling in identity linking flows; no out-of-scope modifications were introduced.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/core/auth-js/src/GoTrueClient.ts`:
- Around line 2048-2067: Replace the direct call to this.__loadSession() in
_getSessionFromURL with this._useSession() so the session load honors the
documented locking contract; call _useSession to obtain the same { data:
sessionData, error: sessionError } result (if _useSession uses a callback API,
invoke it to read session state inside the provided callback) and keep the
existing error handling, session existence check, creation of the Session object
(provider_token/provider_refresh_token), clearing window.location.hash, _debug
call, and the _returnResult({ data: { session, redirectType: params.type },
error: null }) return path.

} = params

if (!access_token || !expires_in || !refresh_token || !token_type) {
if (provider_token || provider_refresh_token) {
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.

🟠 Severity: HIGH

CSRF/Token Injection Vulnerability: This code allows arbitrary provider_token and provider_refresh_token values from URL fragments to be injected into existing sessions without validation. An attacker can craft a malicious URL and trick users into visiting it, causing attacker-controlled provider tokens to be merged into the victim's session. There's no CSRF protection (state parameter), origin validation, or server-side verification that these tokens are legitimate.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: This vulnerability requires server-side validation and CSRF protection. Implement the following multi-step fix: (1) Add a 'state' parameter to the OAuth flow that's generated server-side and validated on callback. Store the state parameter securely (e.g., in session storage with CSRF token) before initiating the OAuth flow. (2) Before merging provider_token and provider_refresh_token into the session, validate the state parameter from the URL matches the stored value. (3) Send the provider tokens to the auth server endpoint (e.g., POST to /token/verify) to verify they were issued for the current authenticated user before accepting them. (4) Only merge the tokens if both state validation and server-side token verification succeed. (5) Consider rejecting provider-token-only callbacks entirely if they're not expected in the linkIdentity flow, or require them to come with additional proof of authenticity from the auth server.

Copy link
Copy Markdown
Author

@samarth212 samarth212 Jan 28, 2026

Choose a reason for hiding this comment

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

Updated to use _useSession in _getSessionFromURL for provider‑token‑only callbacks to honor the documented locking contract.

We only merge provider tokens when a valid session already exists (user must be signed in), and the callback is handled by the Supabase auth redirect flow. Still, if maintainers want stricter validation or a type=link gate, I can add that in a follow‑up.

@mandarini mandarini force-pushed the fix/link-identity-provider-token branch from cdb5220 to c30e189 Compare March 10, 2026 16:02
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 10, 2026

Open in StackBlitz

@supabase/auth-js

npm i https://pkg.pr.new/@supabase/auth-js@2069

@supabase/functions-js

npm i https://pkg.pr.new/@supabase/functions-js@2069

@supabase/postgrest-js

npm i https://pkg.pr.new/@supabase/postgrest-js@2069

@supabase/realtime-js

npm i https://pkg.pr.new/@supabase/realtime-js@2069

@supabase/storage-js

npm i https://pkg.pr.new/@supabase/storage-js@2069

@supabase/supabase-js

npm i https://pkg.pr.new/@supabase/supabase-js@2069

commit: 7454202

@mandarini
Copy link
Copy Markdown
Contributor

Hi there @samarth212 , thanks for contributing to Supabase! :D

I do have some changes to request on your PR!

  1. PR Title needs conventional commit format

  2. I think the premise may be incorrect, as the server always sends full session tokens. After checking the server code at internal/api/external.go and internal/tokens/service.go, for implicit flow OAuth callbacks the server always issues a full session alongside provider tokens:

  // external.go ~L268
  } else if token != nil {
      q := url.Values{}
      q.Set("provider_token", providerAccessToken)
      if providerRefreshToken != "" {
          q.Set("provider_refresh_token", providerRefreshToken)
      }
      rurl = token.AsRedirectURL(rurl, q)  // always adds access_token, refresh_token, expires_in, token_type
  }

and

  // tokens/service.go ~L144
  func (r *AccessTokenResponse) AsRedirectURL(...) string {
      extraParams.Set("access_token", r.Token)
      extraParams.Set("token_type", r.TokenType)
      extraParams.Set("expires_in", strconv.Itoa(r.ExpiresIn))
      extraParams.Set("refresh_token", r.RefreshToken)
      ...
  }

A redirect with provider_token but no access_token is not a scenario the current server produces for implicit flow. The new branch in _getSessionFromURL would never be reached under normal server operation and it can only be triggered by a manually crafted URL. Could you share a reproduction case or server version where this occurs? It's possible the issue originated from an older server behavior or a specific edge case worth documenting.

  1. I do believe there's a security vulnerability for token injection via crafted URL. Because the new branch can only realistically be triggered by crafted URLs (as established above), this is a more acute concern than it first appears. A logged-in user visiting: https://yourapp.com/callback#provider_token=attacker-token would have an attacker-controlled token merged into their session. There's no CSRF defense on the client side here, even though the server does validate CSRF via the flow_state UUID, that protection only covers the server's OAuth processing, not this client-side merge.

  2. I think there are _isImplicitGrantCallback false positives + better signal already exists

  params.provider_token || params.provider_refresh_token  // neither is Supabase-specific

These parameter names are not Supabase-specific. More importantly, the server already stamps every implicit grant redirect with sb= specifically to help clients identify Supabase callbacks:

  // tokens/service.go L150
  extraParams.Set("sb", "")  // "Add Supabase Auth identifier to help clients distinguish Supabase Auth redirects"

If detection needs broadening, params.sb !== undefined is a more reliable and intentional signal.

  1. type=link is not sent by the server for OAuth flows. The PR's test URL uses #type=link, and I suggested gating on params.type === 'link' in my earlier note, but the server only sets type for verify flows (magic links, recovery), not for OAuth redirects. So that gate wouldn't work.

  2. If this PR is reworked to handle a legitimate server-produced scenario, note that the new session spreads the stored user without refreshing it. After linkIdentity, the user's identities array on the server is updated. The returned session would have a stale user.identities list. A _getUser(session.access_token) call (as done in the rest of _getSessionFromURL) would keep it consistent.

  3. Imho test URL is not realistic. The test simulates #provider_token=...&type=link with no access_token, a URL the current server never produces. It would be more valuable to have a test that reflects the actual reproduction case (whatever caused issue linkIdentity has no way to get providerToken #1676).

In summary: Before this can move forward, it would really help to understand the exact server version or configuration that produces a provider-token-only redirect. If that scenario is real, the fix should also consider using params.sb for safer detection, adding a getUser() refresh, and addressing the injection risk. If the root cause turns out to be a PKCE code-exchange issue (where provider tokens aren't forwarded to the client), the fix likely belongs in _exchangeCodeForSession instead.

Once again, thank you very much for your contribution!

Copy link
Copy Markdown
Contributor

@mandarini mandarini left a comment

Choose a reason for hiding this comment

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

Please read my comment

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.

linkIdentity has no way to get providerToken

2 participants