{"record":{"id":"d91262ab74d868d1","repo":"nextauthjs/next-auth","slug":"state-could-not-be-decoded","errorCode":null,"errorMessage":"State could not be decoded","messagePattern":"State could not be decoded","errorType":"exception","errorClass":"InvalidCheck","httpStatus":null,"severity":"error","filePath":"packages/core/src/lib/actions/callback/oauth/checks.ts","lineNumber":202,"sourceCode":"  /**\n   * Returns state if the provider is configured to use state,\n   * and clears the container cookie afterwards.\n   * An error is thrown if the state is missing or invalid.\n   */\n  use: useCookie(\"state\", \"state\"),\n  /** Decodes the state. If it could not be decoded, it throws an error. */\n  async decode(state: string, options: InternalOptions) {\n    try {\n      options.logger.debug(\"DECODE_STATE\", { state })\n      const payload = await decode<EncodedState>({\n        secret: options.jwt.secret,\n        token: state,\n        salt: encodedStateSalt,\n      })\n      if (payload) return payload\n      throw new Error(\"Invalid state\")\n    } catch (error) {\n      throw new InvalidCheck(\"State could not be decoded\", { cause: error })\n    }\n  },\n}\n\nexport const nonce = {\n  async create(options: InternalOptions<\"oidc\">) {\n    if (!options.provider.checks.includes(\"nonce\")) return\n    const value = o.generateRandomNonce()\n    const cookie = await sealCookie(\"nonce\", value, options)\n    return { cookie, value }\n  },\n  /**\n   * Returns nonce if the provider is configured to use nonce,\n   * and clears the container cookie afterwards.\n   * An error is thrown if the nonce is missing or invalid.\n   * @see https://openid.net/specs/openid-connect-core-1_0.html#NonceNotes\n   * @see https://danielfett.de/2020/05/16/pkce-vs-nonce-equivalent-or-not/#nonce\n   */","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/nextauthjs/next-auth/blob/a1a16a5a7780488c7449feece410033f445d0b31/packages/core/src/lib/actions/callback/oauth/checks.ts#L184-L220","documentation":"state.decode wraps any failure while decoding the state token (including the internal \"Invalid state\" throw) into InvalidCheck with the message \"State could not be decoded\" and the original error as `cause`. This is the error developers actually observe when state validation fails at the callback.","triggerScenarios":"Callback receives a `state` query parameter that fails JWT decode/verification against options.jwt.secret with the encodedStateSalt — signature mismatch, malformed token, expired state, or state generated with a different secret.","commonSituations":"Secret mismatch between sign-in and callback servers (load-balanced fleet, per-boot generated secrets); state cookie/token lost or stripped by proxy; user leaving the provider login page open long enough to cross a secret rotation/deploy; browsers pre-fetching or replaying the callback.","solutions":["Pin AUTH_SECRET in env so it's identical on every instance and across deploys.","Retry sign-in from the app — state is single-use and flow-specific.","Check error.cause to distinguish tampering from secret mismatch.","Ensure proxies don't drop the state query parameter or cookies during the redirect chain."],"exampleFix":"// before: ephemeral secret per boot\nconst secret = process.env.NODE_ENV === \"production\" ? undefined : \"dev\"\n// after: explicit persistent secret\nexport const { handlers } = NextAuth({\n  secret: process.env.AUTH_SECRET,\n})","handlingStrategy":"try-catch","validationCode":"if (!process.env.AUTH_SECRET) throw new Error(\"AUTH_SECRET required\")\n// verify all instances agree\n// e.g. log a hash of the secret at boot and compare across pods","typeGuard":null,"tryCatchPattern":"try {\n  const result = await handlers.GET(req)\n} catch (e) {\n  if (e?.message?.includes(\"State could not be decoded\")) {\n    const cause = e.cause // jwt signature/parse error details\n    return Response.redirect(\"/signin?error=StateMismatch\")\n  }\n}","preventionTips":["Pin AUTH_SECRET in environment config, identical on every replica","Avoid deploys/secret rotations while sign-in flows are in progress","Ensure reverse proxies preserve the state query parameter and cookies"],"tags":["oauth","state","csrf","jwt-verification"],"backgroundTag":"oauth-state-decode-failed","analyzedSha":"a1a16a5a7780488c7449feece410033f445d0b31","analyzedAt":"2026-08-28T21:52:38.200Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}