Prevent Attacks and Redirect Users with OAuth 2.0 State Parameters

Authorization protocols provide a state parameter that allows you to restore the previous state of your application. The state parameter preserves some state objects set by the client in the Authorization request and makes it available to the client in the response.

CSRF attacks

The primary reason for using the state parameter is to mitigate CSRF attacks by using a unique and non-guessable value associated with each authentication request about to be initiated. That value allows you to prevent the attack by confirming that the value coming from the response matches the one you sent.

The state parameter is a string so you can encode any other information in it. You send a random value when starting an authentication request and validate the received value when processing the response. You store something on the client application side (in cookies, session, or localstorage) that allows you to perform the validation. If you receive a response with a state that doesn't match, you can infer that you may be the target of an attack because this is either a response for an unsolicited request or someone trying to forge the response.

A CSRF attack specifically targets state-changing requests to initiate an action instead of getting user data because the attacker has no way to see the response to the forged request. For the most basic cases the state parameter should be a nonce, used to correlate the request with the response received from the authentication.

Most modern OIDC and OAuth2 SDKs, including Auth0.js in single-page applications, handle the state generation and validation automatically.

Set and compare state parameter values

  1. Before redirecting a request to the IdP, have the app generate a random string. For example:

    xyzABC123
    
    The allowed length for state is not unlimited. If you get the error 414 Request-URI Too Large, try a smaller value.

  2. Store the string locally. For example:

    storeStateLocally(xyzABC123)
    

  3. Add the state parameter to the request (URL-encoding if necessary). For example:

    // Encode the String   
    tenant.auth0.com/authorize?...&state=xyzABC123
    
    After the request is sent, the user is redirected back to the application by Auth0. The state value will be included in this redirect. Note that depending on the type of connection used, this value might be in the body of the request or in the query string.
    /callback?...&state=xyzABC123
    

  4. Retrieve the returned state value and compare it with the one you stored earlier. If the values match, then approve the authentication response, else deny it.

    // Decode the String
    var decodedString = Base64.decode(encodedString);
    if(receivedState === retrieveStateStoredLocally()) {
     // Authorized request
    } 
    else {
      // This response is not for us, reject it
    }
    

Redirect users

You can use the state parameter to encode an application state that will put the user where they were before the authentication process started. For example, if a user intends to access a protected page in your application, and that action triggers the request to authenticate, you can store that URL to redirect the user back to their intended page after the authentication finishes.

Generate and store a nonce locally (in cookies, session, or local storage) along with any desired state data like the redirect URL. Use the nonce as a state in the protocol message. If the returned state matches the stored nonce, accept the OAuth2 message and fetch the corresponding state data from storage. This is the approach we use in auth0.js.

Use the stored URL to redirect users

  1. Set the nonce state parameter value that you used to mitigate CSRF attacks as explained above.

  2. Store the nonce locally, using it as the key to store all the other application state information such as the URL where the user intended to go. For example:

    {
      "xyzABC123" : {
        redirectUrl: '/protectedResource',
        expiresOn: [...]
      }
    }
    

  3. Authenticate the user, sending the generated nonce as the state.

  4. As part of the callback processing and response validation, verify that the state returned matches the nonce stored locally. If it does, retrieve the rest of the application state (like the redirectUrl).

  5. Once you complete the callback processing, redirect the user to the URL previously stored.

Alternate redirect method

  1. Generate and store a nonce value locally.

  2. Encode any desired state (like the redirect URL) along with the nonce in a protected message (that will need to be encrypted/signed to avoid tampering).

  3. In the response processing, unprotect the message, getting the nonce and other properties stored.

  4. Validate that the included nonce matches what was stored locally and, if so, accept the OAuth2 message.

Limitations and considerations

  • Choose a storage method based on your application type.

    App Type Storage Recommendation
    Regular Web App Cookie or session
    SPA Local browser
    Native App Memory or local

  • From a security perspective, neither the request nor the response is integrity-protected so a user can manipulate them. That is true for adding a parameter to the redirect_uri as well.

  • The allowed length for state parameter value is not unlimited. If you get the error 414 Request-URI Too Large, try a smaller value.

  • Passing URLs in plaintext or in any predictable way is unsafe. Ensure that the state parameter value is:

    • Unique and opaque to ensure that it can be used for defense against CSRF and phishing attacks.

    • If stored in a cookie, it should be signed to prevent forgery.

Learn more