mastra-ai/mastra · error

Google ID token has expired

Error message

Google ID token has expired

What it means

After signature verification, verifyIdToken additionally checks the token's expiry (hasExpired on the JWT payload) and rejects tokens that are no longer valid. This is a defense-in-depth check on top of jwtVerify so expired Google ID tokens are never mapped to a user.

Source

Thrown at auth/google/src/auth-provider.ts:364

    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;
  }

  private isHostedDomainAllowed(hostedDomain: string | undefined): boolean {
    if (this.allowedDomains.length === 0) return true;
    const domain = normalizeDomain(hostedDomain);
    if (!domain) return false;

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Obtain a fresh ID token by re-running the OAuth login flow (or use refresh tokens per Google's guidance) instead of re-verifying an old one.
  2. Verify the token promptly after receiving it in the callback; don't persist and re-verify later.
  3. Check server clock sync (NTP) if the token appears expired immediately after issuance.
  4. In tests, mint tokens with an exp far enough in the future for the test duration.

Example fix

// before (re-verifying cached token)
const user = await provider.verifyIdToken(cache.get('idToken'), nonce);

// after (fresh token per flow)
const user = await provider.verifyIdToken(callbackToken, nonce);
Defensive patterns

Strategy: try-catch

Validate before calling

const { payload } = decodeJwt(token);
if (typeof payload.exp === 'number' && payload.exp * 1000 < Date.now()) {
  // token already expired: trigger re-auth instead of calling verifyIdToken
}

Try / catch

try {
  const user = await provider.verifyIdToken(token, nonce);
} catch (err) {
  if (err instanceof Error && err.message === 'Google ID token has expired') {
    // send the user back through the OAuth flow for a fresh token
    return res.redirect(await provider.getLoginUrl(redirectUri, state));
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling verifyIdToken with a token whose exp claim is in the past — e.g. a cached ID token reused after its ~1 hour lifetime, or a clock-skewed server evaluating a still-valid token as expired.

Common situations: Storing the ID token and re-verifying it on later requests instead of using refresh tokens; replaying old tokens in integration tests; server clocks drifting significantly from real time.

Related errors


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