{"record":{"id":"a26f35af9800b7e5","repo":"mastra-ai/mastra","slug":"token-exchange-failed-error","errorCode":null,"errorMessage":"Token exchange failed: ${error}","messagePattern":"Token exchange failed: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"auth/auth0/src/index.ts","lineNumber":546,"sourceCode":"      const { redirectUri } = verifyStateToken(signedState, self.cookiePassword);\n\n      // Exchange code for tokens\n      const tokenResponse = await fetch(`https://${self.domain}/oauth/token`, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({\n          grant_type: 'authorization_code',\n          client_id: self.clientId,\n          client_secret: self.clientSecret,\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 from ID token or userinfo endpoint\n      let user: EEUser;\n      if (tokens.id_token) {\n        try {\n          const JWKS = createRemoteJWKSet(new URL(`https://${self.domain}/.well-known/jwks.json`));\n          const { payload } = await jwtVerify(tokens.id_token, JWKS, {\n            issuer: `https://${self.domain}/`,\n            audience: self.clientId!, // Validate token was issued for this client","sourceCodeStart":528,"sourceCodeEnd":564,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/auth/auth0/src/index.ts#L528-L564","documentation":"During the SSO OAuth callback, the provider exchanges the authorization code for tokens at Auth0's /oauth/token endpoint. This error wraps the raw error body returned by Auth0 when the HTTP response is not ok (e.g. 400/401/403), so the upstream message (invalid code, bad client credentials, redirect mismatch, etc.) is surfaced verbatim. It is a network/API-response failure, not a local validation.","triggerScenarios":"The token POST inside the SSO callback handler returns a non-ok status — e.g. the authorization code was already used or expired (invalid_grant), client_id/client_secret are wrong (invalid_client), or the redirect_uri in the exchange doesn't exactly match the one used in the login URL or Auth0 app settings.","commonSituations":"User refreshing the callback page causing code reuse; mismatched redirect URI between /authorize and /token calls; wrong client secret after rotating credentials in Auth0; Auth0 tenant/domain misconfiguration; network issues causing truncated responses.","solutions":["Log the full error body included in the message — it names the exact OAuth error (e.g. invalid_grant, invalid_client)","Check that the redirect_uri used in the token exchange exactly matches the one used in getLoginUrl and is registered in the Auth0 application's Allowed Callback URLs","Verify clientId/clientSecret are current and not rotated/expired in Auth0 dashboard","Redirect the user back to a fresh login flow on invalid_grant (codes are single-use and expire in ~10 minutes)","Confirm the provider's domain points at the correct Auth0 tenant"],"exampleFix":"// before\nconst { originalState, redirectUri } = authServer.redirectUri(state);\nconst tokens = await exchangeCode(code); // throws raw\n// after\ntry {\n  const tokens = await exchangeCode(code);\n} catch (e) {\n  if (String(e.message).includes('invalid_grant')) {\n    return Response.redirect(provider.getLoginUrl(callbackUri, newState()), 302);\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// Cannot be pre-validated locally; but ensure before the flow:\n// 1. redirectUri identical in /authorize and /token\n// 2. clientId/clientSecret current\n// 3. domain matches the Auth0 tenant that issued the code","typeGuard":null,"tryCatchPattern":"try {\n  const tokens = await exchangeAuthorizationCode(code);\n} catch (e) {\n  const msg = e instanceof Error ? e.message : String(e);\n  if (msg.includes('invalid_grant') || msg.includes('Token exchange failed')) {\n    // code expired/reused — restart login\n    return redirectTo(provider.getLoginUrl(callbackUri, newState()));\n  }\n  if (msg.includes('invalid_client')) {\n    throw new Error('Auth0 client credentials invalid — check clientId/clientSecret', { cause: e });\n  }\n  throw e;\n}","preventionTips":["Never reuse or replay authorization codes (single-use, ~10 min TTL)","Keep the redirect_uri byte-identical between authorize and token requests","Rotate client secrets in sync with your deployment config","Inspect the raw Auth0 error body in the thrown message to identify the exact OAuth error","Implement a retry-once-with-fresh-login fallback for transient/expired-code failures"],"tags":["auth0","oauth","token-exchange","network","api-error"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}