mastra-ai/mastra · warning

Invalid state token signature

Error message

Invalid state token signature

What it means

After confirming the two-part format, verifyStateToken recomputes the HMAC-SHA256 signature of the base64 payload with the provider secret and compares it with timingSafeEqual. A mismatch means the state token was forged or altered (or was signed with a different secret), so the provider rejects the OAuth callback to prevent CSRF.

Source

Thrown at auth/clerk/src/index.ts:162

}

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

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

  const payload = JSON.parse(atob(payloadB64!)) as StatePayload;
  if (payload.e < Date.now()) {
    throw new Error('State token has expired');
  }

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

/**
 * Escape special regex characters in a string.
 */
function escapeRegex(str: string): string {
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

/**

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Ensure all server instances use the identical provider secret (same env var value) so signatures validate cluster-wide.
  2. Restart the OAuth flow to obtain a fresh state token signed with the current secret.
  3. Confirm the callback hit the same environment/tenant that initiated the flow (no cross-environment state reuse).
  4. If this fires for legitimate users, check for intermediaries rewriting the state query parameter.

Example fix

// before: secret differs between the instance that issued state and the one validating it
// instance A: new MastraAuthClerk({ secretKey: 'old-secret' })
// instance B: new MastraAuthClerk({ secretKey: 'new-secret' })

// after: share one secret via env across all replicas
const auth = new MastraAuthClerk({ secretKey: process.env.CLERK_AUTH_SECRET });
Defensive patterns

Strategy: try-catch

Validate before calling

if (process.env.CLERK_AUTH_SECRET === undefined || process.env.CLERK_AUTH_SECRET.length < 32) {
  throw new Error('CLERK_AUTH_SECRET must be set identically on all instances before handling OAuth callbacks');
}

Type guard

function isStateSignatureError(e: unknown): e is Error {
  return e instanceof Error && e.message === 'Invalid state token signature';
}

Try / catch

try {
  const { redirectUri } = await verifyStateToken(state, secret);
} catch (e) {
  if (isStateSignatureError(e)) {
    // likely forged or secret mismatch across replicas; do not proceed with OAuth
    return respondBadRequest('state signature mismatch — restart OAuth sign-in');
  }
  throw e;
}

Prevention

When it happens

Trigger: verifyStateToken(stateToken, secret) where timingSafeEqual(signature, expectedSig) is false — state payload/signature tampered with, provider secret changed between flow start and callback (multi-instance deploy with different secrets/env configs), or a state minted by another environment (staging token validated in prod).

Common situations: Rotating or mismatching the Clerk provider secret across server replicas so tokens signed by instance A fail on instance B; staging-to-prod URL reuse; attackers modifying the base64 payload; expired flows replayed after the signature is recomputed with a rotated key.

Related errors


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