mastra-ai/mastra · warning

Invalid state token format

Error message

Invalid state token format

What it means

Clerk OAuth state tokens in this provider are signed strings of the form `<payloadB64>.<hmacSignature>`; verifyStateToken splits on '.' and requires exactly two parts before verifying the HMAC. A token not matching `payload.signature` means it was corrupted, truncated, hand-crafted, or was never issued by this provider's createStateToken.

Source

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

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

/**
 * 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.

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Restart the OAuth flow: generate a fresh state via the provider's redirect flow and use it unmodified in the callback.
  2. Ensure the state query parameter is passed through URL encoding unchanged — do not decode/re-encode or append query params to it.
  3. Verify the same state is round-tripped: the callback URL used by Clerk must be the one generated by the provider.

Example fix

// before: manually rebuilding the callback URL and dropping part of state
const url = `/auth/callback?state=${state.split('.')[0]}`;

// after: pass state through verbatim
const url = `/auth/callback?state=${encodeURIComponent(state)}`;
Defensive patterns

Strategy: try-catch

Validate before calling

const state = new URL(callbackUrl).searchParams.get('state');
if (!state || state.split('.').length !== 2) {
  return respondBadRequest('state parameter is malformed; restart the OAuth flow');
}

Type guard

function isWellFormedStateToken(state: string | null | undefined): state is string {
  return typeof state === 'string' && /^[A-Za-z0-9+/=_-]+\.[A-Za-z0-9+/=_-]+$/.test(state);
}

Try / catch

try {
  const { redirectUri } = await verifyStateToken(state, secret);
} catch (e) {
  if (e instanceof Error && e.message === 'Invalid state token format') {
    return respondBadRequest('state mismatch — restart OAuth sign-in');
  }
  throw e;
}

Prevention

When it happens

Trigger: verifyStateToken(stateToken, secret) where stateToken.split('.').length !== 2 — callback query param ?state= missing its signature part, double-encoding mangling the dot, or an attacker/random string supplied as state in the OAuth redirect callback.

Common situations: Reverse proxies or frameworks re-encoding the state query parameter; client-side code trimming/altering the state value; an attacker probing the OAuth callback endpoint (this check correctly rejects them); copying a state from a different environment.

Related errors


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