mastra-ai/mastra · error · Error
Token exchange failed: ${error}
Error message
Token exchange failed: ${error} What it means
Thrown by handleCallback when the HTTP POST to Okta's /v1/token endpoint (exchanging the authorization code for tokens using client_secret) returns a non-OK response. The error message embeds Okta's raw error body, typically invalid_grant or invalid_client.
Source
Thrown at auth/okta/src/auth-provider.ts:438
}
// Exchange code for tokens using client_secret (confidential client)
const tokenResponse = await fetch(`${this.endpointBase}/v1/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${btoa(`${this.clientId}:${this.clientSecret}`)}`,
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: stored.redirectUri,
}),
});
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;
};
// Verify and decode ID token
const { payload: idTokenPayload } = await jwtVerify(tokens.id_token, this.jwks, {
issuer: this.issuer,
audience: this.clientId,
});
const user = mapOktaClaimsToUser(idTokenPayload);
// Create encrypted session cookie.View on GitHub (pinned to 75dd419e61)
Solutions
- Read the embedded error body: invalid_grant means restart the login flow (code is single-use/expired); invalid_client means fix clientId/clientSecret.
- Ensure the redirect_uri sent to the token endpoint exactly matches the authorize request and the Okta app whitelist.
- Verify OKTA_CLIENT_SECRET and OKTA_CLIENT_ID are current and for the correct Okta domain/app.
- Check Okta system logs (admin console) for the failed token request details; retry after transient 5xx/outage.
Example fix
// before (mismatched redirect between authorize and token exchange) redirectUri: 'https://localhost:3000/callback' // after (use the exact same, whitelisted URI in both steps and Okta app settings) redirectUri: process.env.OKTA_REDIRECT_URI // e.g. https://app.example.com/api/auth/sso/okta/callback
Defensive patterns
Strategy: try-catch
Validate before calling
// before starting the flow, ensure redirect URI matches the one the callback will send
if (authorizeRedirectUri !== process.env.OKTA_REDIRECT_URI) {
throw new Error('redirect_uri mismatch between authorize and token exchange');
} Try / catch
try {
await provider.handleCallback(code, stateId);
} catch (e) {
if (e instanceof Error && e.message.startsWith('Token exchange failed:')) {
if (e.message.includes('invalid_grant')) {
// restart SSO flow — code is single-use or expired
} else if (e.message.includes('invalid_client')) {
// check OKTA_CLIENT_ID / OKTA_CLIENT_SECRET / domain
}
} else throw e;
} Prevention
- Always send the identical redirect_uri in authorize and token requests.
- Rotate client secrets carefully and redeploy immediately after rotation.
- Handle browser refresh on callback: redirect to login instead of replaying the code.
- Monitor Okta system log for token endpoint failures.
When it happens
Trigger: Token exchange fails because: the authorization code was already used or expired (invalid_grant), redirect_uri doesn't exactly match the one used in the authorize request, clientId/clientSecret are wrong (invalid_client), or the Okta app/endpoint is misconfigured or unreachable.
Common situations: Callback replayed after refresh (code single-use); environment mismatch where callback uses a different redirect URI than the authorize request; rotated/incorrect client secret; Okta outage returning 5xx; dev/prod Okta domain mixups.
Related errors
- Token exchange failed: ${error}
- Token exchange failed: ${error}
- Google token exchange failed: ${error}
- Redirect URI is required for SSO. Set AUTH0_REDIRECT_URI or
- Failed to fetch user info from Auth0
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/a10e5ca1eeb13a11.
Report an issue: GitHub.