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

  1. Pass the exact nonce that was embedded in the state token for this login attempt (as returned by verifyStateToken / stored in the session).
  2. Restart the login flow (new getLoginUrl) if the original attempt's nonce is unknown; never reuse nonces across attempts.
  3. In tests, use the same nonce when minting the token and verifying it (e.g. deterministic test nonce).
  4. 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

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


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/78c08bf6f9eb1f8f. Report an issue: GitHub.