nextauthjs/next-auth · error

Invalid state

Error message

Invalid state

What it means

state.decode throws the plain Error "Invalid state" when JWT-decoding the state token with options.jwt.secret and the encoded-state salt returns no payload. It is immediately wrapped into InvalidCheck("State could not be decoded"), so developers normally see that outer message with this as cause.

Source

Thrown at packages/core/src/lib/actions/callback/oauth/checks.ts:200

    return { cookie, value }
  },
  /**
   * Returns state if the provider is configured to use state,
   * and clears the container cookie afterwards.
   * An error is thrown if the state is missing or invalid.
   */
  use: useCookie("state", "state"),
  /** Decodes the state. If it could not be decoded, it throws an error. */
  async decode(state: string, options: InternalOptions) {
    try {
      options.logger.debug("DECODE_STATE", { state })
      const payload = await decode<EncodedState>({
        secret: options.jwt.secret,
        token: state,
        salt: encodedStateSalt,
      })
      if (payload) return payload
      throw new Error("Invalid state")
    } catch (error) {
      throw new InvalidCheck("State could not be decoded", { cause: error })
    }
  },
}

export const nonce = {
  async create(options: InternalOptions<"oidc">) {
    if (!options.provider.checks.includes("nonce")) return
    const value = o.generateRandomNonce()
    const cookie = await sealCookie("nonce", value, options)
    return { cookie, value }
  },
  /**
   * Returns nonce if the provider is configured to use nonce,
   * and clears the container cookie afterwards.
   * An error is thrown if the nonce is missing or invalid.
   * @see https://openid.net/specs/openid-connect-core-1_0.html#NonceNotes

View on GitHub (pinned to a1a16a5a77)

Solutions

  1. Set a single explicit AUTH_SECRET shared across all instances and stable across deploys.
  2. Have the user restart the sign-in flow to generate fresh state.
  3. Inspect error.cause for JWT verification details (signature/expiry).
  4. Don't hand-edit or reuse old callback URLs.
Defensive patterns

Strategy: try-catch

Validate before calling

if (!process.env.AUTH_SECRET) throw new Error("AUTH_SECRET required for state signing")

Try / catch

try {
  const session = await auth()
} catch (e) {
  if (e?.message?.includes("State could not be decoded")) {
    // redirect user to start sign-in again
  }
}

Prevention

When it happens

Trigger: The state query parameter on the callback cannot be decoded into an EncodedState payload — expired/tampered JWT, wrong AUTH_SECRET, or a state value not produced by this app.

Common situations: Multi-instance deployments with different secrets; state JWT from a previous deploy/secret; manually edited callback URLs; very old in-flight sign-in attempts after a secret rotation.

Related errors


AI-assisted analysis of nextauthjs/next-auth@a1a16a5a77 (2026-08-28). Data as JSON: /api/errors/eebe515e13af87b1. Report an issue: GitHub.