medusajs/medusa · error · MedusaError
Could not exchange token, ${r.status}, ${r.statusText}
Error message
Could not exchange token, ${r.status}, ${r.statusText} What it means
In the Google OAuth callback, the provider POSTs the authorization code to Google's token endpoint. A non-2xx response triggers INVALID_DATA with the HTTP status and reason.
Source
Thrown at packages/modules/providers/auth-google/src/services/google.ts:150
return { success: false, error: "No code provided" }
}
const state = await authIdentityService.getState(query?.state as string)
if (!state) {
return { success: false, error: "No state provided, or session expired" }
}
const params = `client_id=${this.config_.clientId}&client_secret=${this.config_.clientSecret}&code=${code}&redirect_uri=${state.callback_url}&grant_type=authorization_code`
const exchangeTokenUrl = new URL(
`https://oauth2.googleapis.com/token?${params}`
)
try {
const response = await fetch(exchangeTokenUrl.toString(), {
method: "POST",
}).then((r) => {
if (!r.ok) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Could not exchange token, ${r.status}, ${r.statusText}`
)
}
return r.json()
})
const { authIdentity, success } = await this.verify_(
response.id_token as string,
authIdentityService
)
return {
success,
authIdentity,
}
} catch (error) {View on GitHub (pinned to 5e06e544a2)
Solutions
- Verify clientSecret and callbackUrl match the Google Cloud OAuth client configuration exactly
- Restart the flow to obtain a fresh authorization code (single-use, ~10 min TTL)
- Check Google Cloud Console for the client's status and any consent-screen misconfiguration
Defensive patterns
Strategy: retry
Validate before calling
if (!req.query.code || req.query.error) {
return res.redirect('/auth/google/google') // fresh code instead of failing exchange
} Try / catch
try { await provider.validateCallback(req.query) } catch (e) { if (e.type === 'invalid_data') res.redirect(loginUrl) else throw e } Prevention
- Keep client credentials and redirect URI synced with Google Cloud Console
- Restart the flow on exchange failure; codes are single-use
- Set sane session/state TTLs so users don't sit on stale codes
When it happens
Trigger: Expired or already-used authorization code, invalid client_secret/client_id, or redirect_uri mismatch between the auth request and the token exchange.
Common situations: User refreshing or re-visiting the callback URL after the code was consumed, secrets rotated since the flow started, or callbackUrl not matching the registered redirect URI in Google Cloud Console.
Related errors
- Could not exchange token, ${r.status}, ${r.statusText}
- Google clientId is required
- Google clientSecret is required
- Google callbackUrl is required
- Google does not support registration. Use method `authentica
AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27).
Data as JSON: /api/errors/1a9530ea18b50f19.
Report an issue: GitHub.