mastra-ai/mastra · error

Invalid state token payload

Error message

Invalid state token payload

What it means

Once the state token's signature verifies, the base64 payload is JSON-parsed into a StatePayload ({s, r, e}). If the payload is not valid base64-encoded JSON (or atob/JSON.parse throws), verifyStateToken throws 'Invalid state token payload'. This is distinct from the format and signature errors: the token looks structurally fine and authentic, but its content is unreadable.

Source

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

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

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

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

/**
 * Simple HMAC-SHA256 using Web Crypto (sync wrapper for predictable use).
 * Returns base64url-encoded signature.
 */
function hmacSign(data: string, secret: string): string {
  // Use a simple hash-based approach that works synchronously

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Generate state tokens only via createStateToken — don't hand-assemble base64 payloads and sign them yourself.
  2. Ensure the state value survives the HTTP round trip unchanged (proper URL encoding of '.' and '+', no proxy rewriting).
  3. Handle the error as a failed login: reject the callback with a 4xx and restart the OAuth flow; the payload is unrecoverable by design.
  4. If you upgraded the library, invalidate in-flight sessions/tokens created by the old payload format.
  5. Distinguish this catch from expiry: this error means undecodable payload, while 'State token has expired' (payload.e < Date.now()) means a valid but stale token — both resolve by restarting login.

Example fix

// before
const { originalState, redirectUri } = verifyStateToken(state, secret); // throws 'Invalid state token payload'
// after
try {
  var { originalState, redirectUri } = verifyStateToken(state, secret);
} catch {
  return Response.redirect('/login'); // restart OAuth flow
}
Defensive patterns

Strategy: try-catch

Validate before calling

function statePayloadLooksValid(v) {
  if (!isStateToken(v)) return false;
  try {
    const [b64] = v.split('.');
    const payload = JSON.parse(atob(b64));
    return typeof payload.e === 'number' && typeof payload.s === 'string' && typeof payload.r === 'string';
  } catch {
    return false;
  }
}

Type guard

function isStatePayload(p: unknown): p is { s: string; r: string; e: number } {
  return typeof p === 'object' && p !== null &&
    typeof (p as any).s === 'string' &&
    typeof (p as any).r === 'string' &&
    typeof (p as any).e === 'number';
}

Try / catch

try {
  const { originalState, redirectUri } = verifyStateToken(state, secret);
} catch (err) {
  // covers 'Invalid state token payload' and 'State token has expired'
  return Response.redirect(new URL('/login', req.url));
}

Prevention

When it happens

Trigger: A token signed correctly but whose payload decodes to non-JSON (hand-rolled token signing someone else's payload string with hmacSign), a payload encoded with base64url or UTF-8-safe encodings incompatible with atob, or corrupted payload characters introduced in transit.

Common situations: Custom tooling or tests that sign arbitrary strings instead of using createStateToken; an intermediary that mangles the state parameter; a future/other version of the library changed the payload schema while old tokens still pass signature checks with the same secret.

Related errors


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