state_invalid
Failed to decrypt or parse the OAuth state during the callback.
What is it?
When using the cookie state storage strategy (account.storeStateStrategy: "cookie"), Better Auth encrypts
all OAuth state data into a cookie. During the callback, this cookie is decrypted and parsed. The state_invalid
error means the cookie exists but could not be decrypted, or the decrypted content could not be parsed as valid JSON.
This error is specific to the cookie strategy. On the default database strategy, state failures surface as other
codes (for example state_mismatch), depending on what failed.
Common Causes
- Secret rotation mid-flow. The
BETTER_AUTH_SECRETwas changed between the start and the callback of the OAuth flow, so the decryption key no longer matches the one that encrypted the cookie. - Cookie value corrupted in transit. A proxy, CDN, or middleware modified or truncated the cookie value during the redirect.
- Malformed cookie. The cookie was manually altered, or a different cookie with a conflicting name was read instead.
How to resolve
Avoid rotating secrets during active flows
Deploy secret changes during low-traffic windows. If you need to rotate secrets, consider running both the old and new secrets simultaneously during a transition period so in-progress flows can complete.
Check proxies and middleware
Verify that any reverse proxies, CDNs, or middleware in front of your application preserve the full, unmodified cookie value. Look for cookie rewriting, truncation, or URL-encoding issues.
Verify cookie names and values
Use your browser's DevTools (Application, then Cookies) to confirm that the better-auth.oauth_state cookie is set
before the redirect and still exists (unmodified) when the callback arrives.
Switch to the database strategy
If cookie issues persist, switch to the default database strategy, which does not depend on a state cookie for decryption:
export const auth = betterAuth({
account: {
storeStateStrategy: "database",
},
});For an overview of all state-related errors and their root causes, see state_mismatch.