mastra-ai/mastra · error

Invalid state token format

Error message

Invalid state token format

What it means

Google's state token format is `<base64Payload>.<hmacSignature>`. verifyStateToken splits on '.', and if the split doesn't yield exactly two parts the string structurally cannot be a token this library issued, so it throws before any signature check. This guards against garbage, URL-mangled, or foreign-format state values reaching the HMAC verification path.

Source

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

): Promise<string> {
  const payload: StatePayload = {
    s: originalState,
    r: redirectUri,
    e: Date.now() + STATE_TOKEN_EXPIRY_MS,
    n: nonce,
  };
  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');
  }

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Restart the login flow so the callback receives a freshly issued `<payload>.<signature>` state token.
  2. Log the received state (shape only) before verification and check for mangling — missing dot, extra dots, stripped characters.
  3. Ensure the callback route reads the raw `state` query parameter without extra decoding/transformation.
  4. Clear stale cookies or stored state from older versions and ensure the state round-trips through your frontend untouched.

Example fix

// before: verify whatever arrives
const result = await verifyStateToken(req.query.state as string, secret);
// after
const state = req.query.state;
if (typeof state !== 'string' || !/^[A-Za-z0-9+/=]+\.[A-Za-z0-9+/=]+$/.test(state)) {
  return res.redirect('/auth/google/login'); // malformed state, re-issue
}
const result = await verifyStateToken(state, secret);
Defensive patterns

Strategy: validation

Validate before calling

function hasValidStateShape(state: unknown): state is string {
  return typeof state === 'string' && state.split('.').length === 2 && state.length > 0;
}
if (!hasValidStateShape(req.query.state)) return redirectToLogin();

Type guard

function isStateToken(v: unknown): v is string {
  return typeof v === 'string' && /^[^.]+\.[^.]+$/.test(v);
}

Try / catch

try {
  await verifyStateToken(state, secret);
} catch (e) {
  if ((e as Error).message === 'Invalid state token format') {
    return restartLoginFlow(); // stale or mangled state
  }
  throw e;
}

Prevention

When it happens

Trigger: The OAuth callback passes a state value whose split('.') length !== 2 — an empty state, a state that was URL-decoded/re-encoded losing the dot or gaining extra dots, a plaintext CSRF value from an older flow, or a JWT (multiple dots) passed instead.

Common situations: Middleware or framework double-decoding query params and mangling the token; frontend truncating the state query parameter; old cookies/stored state from a previous library version with a different format; passing a signed JWT where the library's state token is expected.

Related errors


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