{"record":{"id":"ae2f1f2a235c35b3","repo":"mastra-ai/mastra","slug":"token-exchange-failed-error-ae2f1f","errorCode":null,"errorMessage":"Token exchange failed: ${error}","messagePattern":"Token exchange failed: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"auth/clerk/src/index.ts","lineNumber":566,"sourceCode":"\n      // Exchange code for tokens using client_secret (confidential client)\n      const tokenResponse = await fetch(`${self.fapiUrl}/oauth/token`, {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/x-www-form-urlencoded',\n          Authorization: `Basic ${btoa(`${self.oauthClientId}:${self.oauthClientSecret}`)}`,\n        },\n        body: new URLSearchParams({\n          grant_type: 'authorization_code',\n          code,\n          redirect_uri: redirectUri,\n        }),\n        signal: AbortSignal.timeout(10_000), // 10 second timeout\n      });\n\n      if (!tokenResponse.ok) {\n        const error = await tokenResponse.text();\n        throw new Error(`Token exchange failed: ${error}`);\n      }\n\n      const tokens = (await tokenResponse.json()) as {\n        access_token: string;\n        id_token?: string;\n        refresh_token?: string;\n        expires_in: number;\n        token_type: string;\n      };\n\n      // Get user info — try ID token first, fall back to userinfo endpoint\n      let user: EEUser;\n      if (tokens.id_token) {\n        const payload = await verifyJwks(tokens.id_token, self.jwksUri);\n        user = {\n          id: payload.sub!,\n          email: (payload.email as string) ?? undefined,\n          name: (payload.name as string) ?? undefined,","sourceCodeStart":548,"sourceCodeEnd":584,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/auth/clerk/src/index.ts#L548-L584","documentation":"After the OAuth redirect, the provider exchanges the authorization code for tokens at Clerk's OAuth token endpoint using client credentials, with a 10-second timeout. Any non-2xx response (invalid code, expired code, wrong client secret, redirect_uri mismatch, Clerk outage) causes the library to read the response body and throw `Token exchange failed: <body>`, surfacing the upstream OAuth error text to the caller.","triggerScenarios":"The SSO callback handler runs the code-for-token fetch and Clerk replies with !tokenResponse.ok — e.g. error=invalid_grant (code already used or expired), error=invalid_client (bad oauthClientSecret), or redirect_uri not matching the one used in the authorization request.","commonSituations":"User refreshes the callback page, replaying a one-time authorization code; mismatched redirect URI between authorize and token requests; wrong Clerk OAuth client secret per environment; network/proxy issues or Clerk downtime; race where two tabs complete the flow simultaneously.","solutions":["Read the embedded error body in the thrown message — it names the OAuth error (invalid_grant, invalid_client, etc.) — and fix that specific cause.","Ensure the redirectUri used at token exchange exactly matches the one in the authorization request and is registered in Clerk.","Verify oauthClientId/oauthClientSecret belong to the correct Clerk instance/environment.","Handle replays: redirect users away from the callback URL after success so the code isn't reused; consider a single retry for transient 5xx/network errors.","If message indicates timeout, check connectivity to Clerk's FAPI endpoint from your deployment."],"exampleFix":"// before: assume every failure is transient\nreturn handleCallback(req);\n// after\ntry {\n  return await handleCallback(req);\n} catch (e) {\n  if (String((e as Error).message).includes('invalid_grant')) {\n    return res.redirect('/auth/sso/login'); // restart OAuth, code is single-use\n  }\n  throw e;\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return await handleSsoCallback(req);\n} catch (e) {\n  const msg = (e as Error).message;\n  if (msg.startsWith('Token exchange failed:')) {\n    if (msg.includes('invalid_grant')) return restartLoginFlow(); // replayed/expired code\n    if (msg.includes('invalid_client')) throw new Error('Check oauthClientId/secret configuration');\n    if (msg.includes('timeout') || msg.includes('5')) return retryWithBackoff(handleSsoCallback, req, 2);\n  }\n  throw e;\n}","preventionTips":["Redirect users away from the callback URL after success so authorization codes are never replayed.","Keep redirect_uri identical between authorize and token requests and registered in Clerk.","Retry only transient failures (5xx/timeouts); restart the flow for invalid_grant.","Monitor Clerk status and set alerts on token-exchange failure rates."],"tags":["auth","clerk","oauth","network","token-exchange"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}