mastra-ai/mastra · error
Token exchange failed: ${error}
Error message
Token exchange failed: ${error} What it means
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.
Source
Thrown at auth/auth0/src/index.ts:546
const { redirectUri } = verifyStateToken(signedState, self.cookiePassword);
// Exchange code for tokens
const tokenResponse = await fetch(`https://${self.domain}/oauth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
client_id: self.clientId,
client_secret: self.clientSecret,
code,
redirect_uri: redirectUri,
}),
signal: AbortSignal.timeout(10_000), // 10 second timeout
});
if (!tokenResponse.ok) {
const error = await tokenResponse.text();
throw new Error(`Token exchange failed: ${error}`);
}
const tokens = (await tokenResponse.json()) as {
access_token: string;
id_token?: string;
refresh_token?: string;
expires_in: number;
token_type: string;
};
// Get user info from ID token or userinfo endpoint
let user: EEUser;
if (tokens.id_token) {
try {
const JWKS = createRemoteJWKSet(new URL(`https://${self.domain}/.well-known/jwks.json`));
const { payload } = await jwtVerify(tokens.id_token, JWKS, {
issuer: `https://${self.domain}/`,
audience: self.clientId!, // Validate token was issued for this clientView on GitHub (pinned to 75dd419e61)
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
Example fix
// before
const { originalState, redirectUri } = authServer.redirectUri(state);
const tokens = await exchangeCode(code); // throws raw
// after
try {
const tokens = await exchangeCode(code);
} catch (e) {
if (String(e.message).includes('invalid_grant')) {
return Response.redirect(provider.getLoginUrl(callbackUri, newState()), 302);
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Cannot be pre-validated locally; but ensure before the flow: // 1. redirectUri identical in /authorize and /token // 2. clientId/clientSecret current // 3. domain matches the Auth0 tenant that issued the code
Try / catch
try {
const tokens = await exchangeAuthorizationCode(code);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
if (msg.includes('invalid_grant') || msg.includes('Token exchange failed')) {
// code expired/reused — restart login
return redirectTo(provider.getLoginUrl(callbackUri, newState()));
}
if (msg.includes('invalid_client')) {
throw new Error('Auth0 client credentials invalid — check clientId/clientSecret', { cause: e });
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Failed to fetch user info from Auth0
- Token exchange failed: ${error}
- Token exchange failed: ${error}
- Invalid state token format
- Invalid or tampered state token
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/a26f35af9800b7e5.
Report an issue: GitHub.