Using the State Parameter to Prevent CSRF Attacks

How the OAuth 2.0 state parameter prevents cross-site request forgery attacks and login CSRF. Implementation guide with examples.

Security

Detailed Explanation

The State Parameter and CSRF Protection

The state parameter is a critical security measure in OAuth 2.0 that prevents Cross-Site Request Forgery (CSRF) attacks during the authorization flow. Without it, an attacker can trick a user into authorizing the attacker's account in the victim's session.

The Attack Without State

  1. The attacker starts an OAuth flow with the victim application and gets an authorization code
  2. The attacker constructs a URL: https://victim-app.com/callback?code=ATTACKER_CODE
  3. The attacker tricks the victim into clicking the link (via email, forum post, etc.)
  4. The victim's browser sends the request to the victim app
  5. The victim app exchanges the attacker's code for a token and links the attacker's external account to the victim's session
  6. The attacker now has access to the victim's account

How State Prevents This

  1. Before redirect: Generate a cryptographically random string and store it in the session:
const state = crypto.randomUUID();
sessionStorage.setItem("oauth_state", state);
// Include state in the authorization URL
  1. On callback: Verify the state matches:
const urlParams = new URLSearchParams(window.location.search);
const returnedState = urlParams.get("state");
const storedState = sessionStorage.getItem("oauth_state");

if (returnedState !== storedState) {
  throw new Error("State mismatch — possible CSRF attack");
}
sessionStorage.removeItem("oauth_state");
// Proceed with token exchange

State Best Practices

Practice Reason
Use crypto.randomUUID() or 32+ random bytes Prevent guessing
Store in session (not localStorage) Expires with session
One-time use Prevent replay attacks
Include in every authorization request Defense in depth

Advanced: Encoding Data in State

You can embed additional information (like the return URL) by encoding it:

const statePayload = JSON.stringify({
  nonce: crypto.randomUUID(),
  returnTo: "/dashboard",
});
const state = btoa(statePayload);

On callback, decode and verify the nonce, then redirect to the encoded returnTo URL.

Use Case

Any web application implementing the Authorization Code or Authorization Code + PKCE flow. The state parameter should be included in every authorization request to protect users from CSRF and login CSRF attacks.

Try It — OAuth 2.0 Flow Visualizer

Open full tool