{"record":{"id":"6261e98ff007af48","repo":"mastra-ai/mastra","slug":"invalid-state-token-format-6261e9","errorCode":null,"errorMessage":"Invalid state token format","messagePattern":"Invalid state token format","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"auth/google/src/auth-provider.ts","lineNumber":189,"sourceCode":"): Promise<string> {\n  const payload: StatePayload = {\n    s: originalState,\n    r: redirectUri,\n    e: Date.now() + STATE_TOKEN_EXPIRY_MS,\n    n: nonce,\n  };\n  const payloadB64 = btoa(JSON.stringify(payload));\n  const signature = await hmacSign(payloadB64, secret);\n  return `${payloadB64}.${signature}`;\n}\n\nasync function verifyStateToken(\n  stateToken: string,\n  secret: string,\n): Promise<{ originalState: string; redirectUri: string; nonce: string }> {\n  const parts = stateToken.split('.');\n  if (parts.length !== 2) {\n    throw new Error('Invalid state token format');\n  }\n\n  const [payloadB64, signature] = parts as [string, string];\n  const expectedSig = await hmacSign(payloadB64, secret);\n  if (!timingSafeEqual(signature, expectedSig)) {\n    throw new Error('Invalid state token signature');\n  }\n\n  let payload: StatePayload;\n  try {\n    payload = JSON.parse(atob(payloadB64)) as StatePayload;\n  } catch {\n    throw new Error('Invalid state token payload');\n  }\n\n  if (payload.e < Date.now()) {\n    throw new Error('State token has expired');\n  }","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/auth/google/src/auth-provider.ts#L171-L207","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Restart the login flow so the callback receives a freshly issued `<payload>.<signature>` state token.","Log the received state (shape only) before verification and check for mangling — missing dot, extra dots, stripped characters.","Ensure the callback route reads the raw `state` query parameter without extra decoding/transformation.","Clear stale cookies or stored state from older versions and ensure the state round-trips through your frontend untouched."],"exampleFix":"// before: verify whatever arrives\nconst result = await verifyStateToken(req.query.state as string, secret);\n// after\nconst state = req.query.state;\nif (typeof state !== 'string' || !/^[A-Za-z0-9+/=]+\\.[A-Za-z0-9+/=]+$/.test(state)) {\n  return res.redirect('/auth/google/login'); // malformed state, re-issue\n}\nconst result = await verifyStateToken(state, secret);","handlingStrategy":"validation","validationCode":"function hasValidStateShape(state: unknown): state is string {\n  return typeof state === 'string' && state.split('.').length === 2 && state.length > 0;\n}\nif (!hasValidStateShape(req.query.state)) return redirectToLogin();","typeGuard":"function isStateToken(v: unknown): v is string {\n  return typeof v === 'string' && /^[^.]+\\.[^.]+$/.test(v);\n}","tryCatchPattern":"try {\n  await verifyStateToken(state, secret);\n} catch (e) {\n  if ((e as Error).message === 'Invalid state token format') {\n    return restartLoginFlow(); // stale or mangled state\n  }\n  throw e;\n}","preventionTips":["Read the raw state query param without extra URL decoding.","Clear stale state from previous flows/library versions before verifying.","Check state shape (one dot, two non-empty parts) client- and server-side before calling verify."],"tags":["auth","google","oauth","state-validation","csrf"],"backgroundTag":"oauth-state-invalid-format","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}