mastra-ai/mastra · error

Invalid state token signature

Error message

Invalid state token signature

What it means

After confirming the `payload.signature` shape, verifyStateToken recomputes an HMAC over the base64 payload with the provider's secret and compares it to the attached signature using a timing-safe equality. A mismatch means the payload was altered, the signature is from a different secret, or the token was forged/copied from another environment — so verification fails closed.

Source

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

  };
  const payloadB64 = btoa(JSON.stringify(payload));
  const signature = await hmacSign(payloadB64, secret);
  return `${payloadB64}.${signature}`;
}

async function verifyStateToken(
  stateToken: string,
  secret: string,
): Promise<{ originalState: string; redirectUri: string; nonce: string }> {
  const parts = stateToken.split('.');
  if (parts.length !== 2) {
    throw new Error('Invalid state token format');
  }

  const [payloadB64, signature] = parts as [string, string];
  const expectedSig = await hmacSign(payloadB64, secret);
  if (!timingSafeEqual(signature, expectedSig)) {
    throw new Error('Invalid state token signature');
  }

  let payload: StatePayload;
  try {
    payload = JSON.parse(atob(payloadB64)) as StatePayload;
  } catch {
    throw new Error('Invalid state token payload');
  }

  if (payload.e < Date.now()) {
    throw new Error('State token has expired');
  }

  return {
    originalState: payload.s,
    redirectUri: payload.r,
    nonce: payload.n,
  };

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Ensure the exact same secret (env var / provider options) is deployed to every instance that can serve the OAuth callback.
  2. If secrets were recently rotated, have affected users restart login — previously issued state tokens can't verify against the new secret.
  3. Restart the flow to get a fresh state token if the error is one-off (rules out transient tampering).
  4. Check that no middleware re-encodes the state (base64url vs base64, padding) between issuance and verification.

Example fix

// before
const provider = new GoogleAuthProvider({ /* secret pulled from per-instance config */ });
// after: pin one secret everywhere
const secret = process.env.OAUTH_STATE_SECRET;
if (!secret) throw new Error('OAUTH_STATE_SECRET must be set identically on all replicas');
const provider = new GoogleAuthProvider({ stateSecret: secret });
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await verifyStateToken(state, secret);
} catch (e) {
  if ((e as Error).message === 'Invalid state token signature') {
    logger.warn('State signature mismatch — check secret consistency across replicas or secret rotation');
    return restartLoginFlow();
  }
  throw e;
}

Prevention

When it happens

Trigger: verifyStateToken computes `expectedSig = hmacSign(payloadB64, secret)` and `timingSafeEqual(signature, expectedSig)` returns false during Google SSO callback verification.

Common situations: Different CLERK/Google provider secret (or cookiePassword-derived secret) between the instance that issued the token and the instance verifying it — e.g. multiple server replicas with diverging env vars, or rotating secrets without invalidating in-flight logins; payload tampering; state token copied from a dev environment into prod; base64 re-encoding by a proxy altering the signed bytes.

Related errors


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