mastra-ai/mastra · error
Invalid Google ID token nonce
Error message
Invalid Google ID token nonce
What it means
verifyIdToken validates a Google-issued ID token (signature via JWKS, issuer, audience) and, when a nonce was supplied, compares it to the nonce claim embedded in the token. A mismatch means the token was not issued in response to the authentication request that carried this nonce, breaking replay protection, so the library rejects the token.
Source
Thrown at auth/google/src/auth-provider.ts:360
return [...this.allowedDomains];
}
getHostedDomain(): string | undefined {
return this.hostedDomain;
}
getClientId(): string {
return this.clientId;
}
private async verifyIdToken(token: string, nonce?: string): Promise<GoogleUser> {
const { payload } = await jwtVerify(token, this.jwks, {
issuer: GOOGLE_ISSUERS,
audience: this.clientId,
});
if (nonce && payload.nonce !== nonce) {
throw new Error('Invalid Google ID token nonce');
}
if (hasExpired(payload)) {
throw new Error('Google ID token has expired');
}
const user = mapGoogleClaimsToUser(payload);
if (!user.googleId) {
throw new Error('Google ID token is missing subject');
}
if (!this.isHostedDomainAllowed(user.hostedDomain)) {
throw new Error('Google user is not in an allowed hosted domain');
}
return user;
}
View on GitHub (pinned to 75dd419e61)
Solutions
- Pass the exact nonce that was embedded in the state token for this login attempt (as returned by verifyStateToken / stored in the session).
- Restart the login flow (new getLoginUrl) if the original attempt's nonce is unknown; never reuse nonces across attempts.
- In tests, use the same nonce when minting the token and verifying it (e.g. deterministic test nonce).
- Ensure session storage correctly persists the nonce between the authorize redirect and the callback.
Example fix
// before (nonce regenerated before callback)
const nonce = crypto.randomUUID();
const user = await provider.verifyIdToken(token, nonce);
// after (use nonce bound to this login's state)
const { nonce } = await provider.verifyStateToken(state);
const user = await provider.verifyIdToken(token, nonce); Defensive patterns
Strategy: validation
Validate before calling
const { nonce } = await provider.verifyStateToken(state);
if (!nonce) throw new Error('Missing nonce for this login attempt');
await provider.verifyIdToken(token, nonce); Try / catch
try {
const user = await provider.verifyIdToken(token, expectedNonce);
} catch (err) {
if (err instanceof Error && err.message === 'Invalid Google ID token nonce') {
// possible replay/crossed login flows: reject and restart the OAuth flow
return res.redirect('/login');
}
throw err;
} Prevention
- Persist the nonce from the state token in the user's session and verify against exactly that value.
- Never regenerate the nonce between the authorize redirect and the callback.
- Scope the nonce per login attempt; do not share it across tabs or parallel flows.
- In tests, use a fixed nonce for both token minting and verification.
When it happens
Trigger: Calling verifyIdToken(token, nonce) where payload.nonce differs from the provided nonce — e.g. the nonce came from a different/older login attempt, the state token's nonce was regenerated between the authorize redirect and callback, or two parallel login flows used different nonces.
Common situations: Replaying a recorded ID token in tests; verifying a token obtained from a refresh or a different browser tab; mixing state tokens across login attempts; nonce not persisted per-session so the callback verifies against the wrong value.
Related errors
- Google ID token is missing subject
- Invalid state token format
- Google ID token has expired
- MastraFactory: integration '${integration.id}' signs OAuth s
- State mismatch
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/78c08bf6f9eb1f8f.
Report an issue: GitHub.