0

I'm trying to understand the Authorisation Code flow in OAuth and I'm confused about how CSRF would happen, specifically I don't think I'm understand how the flow actually works.

Here's a diagram of how I think the Auth Code flow works with a server side rendered app.

OAuth Auth Code Flow

I can understand how an attacker could send a victim a link that would log the user into the hacker's account; the hacker can initiase their own Auth flow but then intercept the GET request to /handle-auth and instead send that URL to the victim.

What I'm not sure about is how the state parameter works and how it prevents CSRF.

  • Where is it created in the the flow diagram?
  • Where is it verified?
  • How is the state parameter linked to the user? I know it's something to do with session cookies or sessionIds but I don't understand how that works.
0

1 Answer 1

1

Without the state parameter, the client has no clue whether the authorization code it receives at the redirection endpoint actually belongs to the current user, or whether an attacker has tricked the user into making the request with a code from the attacker. As a result, the client may wrongly associate an attacker-controlled resource with the current user. If the user performs operations on the resource (e.g., adding sensitive data), the client will make corresponding requests to the resource server. As the attacker is the actual resource owner, they can, for example, see added data.

The attack works as follows:

  1. Attacker A creates a protected resource at some resource server.
  2. Then A picks a vulnerable client (e.g., a website) which doesn't use the state parameter and is allowed to send OAuth requests to the resource server. Additionally, A chooses a victim V that is a user of the client.
  3. Now A triggers the OAuth procedure, pretending to give C access to the protected resource. This causes C to construct a URL for the authorization request and send A to that URL.
  4. A authenticates at the authorization server and gives C permission to access the resource. However, instead of following the redirection URL in the authorization response, A copies that URL (which includes the authorization code) and creates a link intended for the victim V.
  5. If V follows the link, this triggers a CSRF attack: V unknowingly makes a request to the redirection endpoint with an authorization code associated with the attacker-controlled resource.
  6. After C has received the authorization code through the redirection endpoint, it will request access to the protected resource, incorrectly assuming that the resource belongs to the current user V.
  7. Whenever V accesses “their” resource through C, they're actually accessing a resource controlled by A. For example, if V adds sensitive data in the resource, C forwards that data to the resource server, and A (who is the actual owner) can see it.

The state parameters is a simple but effective way to fix this: When constructing the URL for the authorization request, the client generates a parameter which binds the request to the currently authenticated user and cannot be forged by an attacker. One possibility is a cryptographic hash of the current session ID (which no other user should know, preventing forgeries). Note that using the session ID itself would be a poor choice, as the URL may be transmitted or stored in an insecure manner (this is explicitly pointed out in the OAuth specification). A cryptographic hash, on the other hand, doesn't reveal the original input.

When the authorization server sends a user to the redirection endpoint, it includes the state parameter it received in the original authorization request, together with the authorization code. This allows the client to check whether the authorization code belongs to the current user or a CSRF attacker which has tricked the current user into following a prepared link. This completely prevents the attack above, because the attacker cannot simply copy their own redirection URL which includes their state parameter. They have to forge/guess a victim's state parameter, and this should be impractical (given a correct implementation).

So the client goes through the following steps when using the state parameter.

  1. When the current user wants to go through the OAuth procedure, the client generates a state parameter which is tied to the current user and cannot be forged, e.g., sha256(current_session_id).
  2. This parameter is added to the URL for the authorization request, together with response_type=token, the client_id, the redirect_uri etc. The user is sent to this URL as usual.
  3. When the authorization server sends the user back to the redirection URL, it includes both the authorization code and the original state. Now the client can check whether the state is associated with the current user, e.g., by checking state == sha256(current_session_id). If this is the case, then the request to the redirection URL has been made by the user as part of the normal OAuth procedure. If the values don't match, then something went wrong, and the client is supposed to reject the request. In the special case that the state belongs to a different user, this strongly indicates a CSRF attack.

As to the diagram:

What you've shown isn't really vanilla OAuth but an authentication protocol on top of OAuth, probably OpenID Connect. The original OAuth isn't intended to provide a log-in via third party sites like Google but to share specific resources (like Instagram photos). The steps are still correct, as far as I can see. However, it's important that the client assembles the URL for the authorization request (which in your case goes to /authorise), so that's where the client generates the state parameter. This parameter is then checked at the redirection endpoint, in your case /handle-auth.

5
  • Thanks for taking the time to write this great explanation. For the /handle-auth check, are you saying this check is done in the browser rather than the server? Commented Nov 10 at 10:26
  • @AS3: No, the check happens on the client backend, at the redirection endpoint which receives the authorization code. Instead of blindly accepting that code and assuming that it belongs to the current user, the backend is supposed to check whether the state which came together with the code matches the current user's state. This works very much like a classical anti-CSRF token: The client sets up a shared secret with the user. When the user makes a request to the redirection endpoint, she is required to include this secret in the state parameter. Commented Nov 10 at 10:53
  • @AS3: A user which follows the legitimate Oauth flow has no problem supplying this value, because the authorization server simply copies the correct state from the authorization request when redirecting the user back to the client’s redirection endpoint. However, a CSRF attacker doesn’t know the victim’s state, so she cannot forge a request for the victim. If she supplies her own state, then the client’s redirection endpoint will immediately recognize that it doesn’t match the victim’s state (which the client can get from, e.g., the current cookies). Commented Nov 10 at 10:54
  • Ok I think what I'm not understanding is how the client backend can link an incoming request to a specific user. Are there other http headers involved or something? I thought http requests were stateless and so if the client backend recieved a request GET some-client.com/handle-auth?authCode=abc&state=hashedSessionId it would'nt have any way of knowing who made the request? Commented Nov 10 at 11:08
  • 1
    @AS3: If you use session cookies, then a request triggered by, e.g., a 302 redirect automatically contains them in the Cookie header (provided the SameSite attribute allows this). Commented Nov 10 at 12:00

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.