mastra-ai/mastra · error

Invalid state token format

Error message

Invalid state token format

What it means

OAuth state tokens are stateless HMAC-signed strings of the form 'base64(payload).base64(signature)'. verifyStateToken splits on '.' and requires exactly two parts; anything else cannot be a well-formed signed token, so it throws 'Invalid state token format' before any signature check.

Source

Thrown at auth/auth0/src/index.ts:122

function createStateToken(originalState: string, redirectUri: string, secret: string): string {
  const payload: StatePayload = {
    s: originalState,
    r: redirectUri,
    e: Date.now() + STATE_TOKEN_EXPIRY_MS,
  };
  const payloadB64 = btoa(JSON.stringify(payload));
  const signature = hmacSign(payloadB64, secret);
  return `${payloadB64}.${signature}`;
}

/**
 * Verify and decode a state token.
 * Returns the original state and redirectUri if valid and not expired.
 */
function verifyStateToken(stateToken: string, secret: string): { originalState: string; redirectUri: string } {
  const parts = stateToken.split('.');
  if (parts.length !== 2) {
    throw new Error('Invalid state token format');
  }

  const [payloadB64, signature] = parts as [string, string];

  // Verify signature
  const expectedSig = hmacSign(payloadB64, secret);
  if (!timingSafeEqual(signature, expectedSig)) {
    throw new Error('Invalid or tampered state token');
  }

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

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Only verify state tokens produced by createStateToken of this library — raw state values and JWTs will never have the payload.signature shape.
  2. Ensure the state parameter is passed through the OAuth round trip verbatim (no extra encode/decode) between the login redirect and the callback handler.
  3. Check your callback route reads the 'state' query param, not another parameter or a merged value.
  4. Clear stale bookmarks/links with old state and start a fresh login flow.
  5. Wrap verification in try/catch and reject the callback (400) without leaking which check failed.

Example fix

// before
const { redirectUri } = verifyStateToken(searchParams.get('state') ?? '', secret);
// after
const state = searchParams.get('state');
if (!state || state.split('.').length !== 2) {
  return new Response('Bad state', { status: 400 });
}
const { redirectUri } = verifyStateToken(state, secret);
Defensive patterns

Strategy: validation

Validate before calling

function isSignedStateToken(v) {
  return typeof v === 'string' && v.length > 0 && v.split('.').length === 2;
}
const state = url.searchParams.get('state');
if (!isSignedStateToken(state)) return new Response('Bad Request', { status: 400 });

Type guard

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

Try / catch

try {
  const { originalState, redirectUri } = verifyStateToken(state, secret);
} catch (err) {
  if (err instanceof Error && err.message === 'Invalid state token format') {
    return new Response('Invalid OAuth state', { status: 400 });
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing a state value to verifyStateToken that contains zero or more than one '.' — e.g. an OAuth state generated by another library (JWTs contain dots), a raw random string without a signature, a double-encoded or re-URL-encoded token where '.' was mangled, or an empty string.

Common situations: Mixing CSRF state from a different auth library or a previous implementation; the round-tripped state was URL-decoded/transformed by a framework or callback handler before verification; user bookmarks an old login URL and the expired/mangled state is replayed; hand-crafting state in tests.

Related errors


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