Several weeks ago when I was researching how to be an OAuth2.0 provider, I found the following potential vulnerability when a user is requesting an access token from OAuth provider.
Senario:
If user A requests a authentication code with state=1234 and he gets redirected to: http://abc.com/callback?state=1234&code=9IDE3F
If user B requests a authentication code with state=5678 and he gets redirected to: http://abc.com/callback?state=5678&code=3R34TG
Let’s assume A and B exchange the callback url with each other, if the application server has a state detecting mechanism, then neither A nor B can get access token because the application server has rejected the requests.
That seems fine. But let’s use a different way:
Assume A and B exchange only “code" and keep their own states.
Then A requests: http://abc.com/callback?state=1234&code=3R34TG
At this time, application server can be easily cheated since it only checks the state and the state ‘1234’ does come from A’s original request.
Once the application server validates the request, access token can be returned to A.
In terms of most popular OAuth2.0 providers (i.e. sina weibo, tencent weibo, even including google oauth2, etc), after analyzed their API documents, I’ve found no one checks the integrity of state and code. ‘State’ is always treated as an optional parameter for them. In another words, ‘state’ is only used for application server to check the validation of the request but not for themselves. Parameter ‘state’ is not applicable for requesting an access token. Once application server is defrauded, I don’t think it makes sense for OAuth providers to give the access token to suspicious user. It’s still their obligation to store users’ information more securely.
(quoted from Google OAuth2 )
Here is an example of a complete OpenID Connect authentication URI, with line breaks and spaces for readability:
https://accounts.google.com/o/oauth2/auth? client_id=424911365001.apps.googleusercontent.com& response_type=code& scope=openid%20email& redirect_uri=https://oa2cb.example.com/& state=security_token%3D138r5719ru3e1%26url%3Dhttps://oa2cb.example.com/myHome& [email protected]& openid.realm=example.com& hd=example.comUsers are required to give consent if your app requests any new information about them, or if your app requests account access that they have not previously approved.
3. Confirm anti-forgery state token
The response is sent to the
redirect_uri
that you specified in the request. All responses are returned in the query string, as shown below:https://oa2cb.example.com/code?state=security_token%3D138r5719ru3e1%26url%3Dhttps://oa2cb.example.com/myHome&code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7On the server, you must confirm that the
state
received from Google matches the session token you created in Step 1. This round-trip verification helps to ensure that the user, not a malicious script, is making the request.4. Exchange
code
for access token and ID tokenThe response includes a
code
parameter, a one-time authorization code that your server can exchange for an access token and ID token. Your server makes this exchange by sending an HTTPSPOST
request. ThePOST
request is sent to the token endpoint, which you should retrieve from theDiscovery document using the keytoken_endpoint
. The following discussion assumes the endpoint ishttps://accounts.google.com/o/oauth2/token
. The request must include the following parameters in thePOST
body:
Field Description code
The authorization code that is returned from the initial request. client_id
The client ID that you obtain from the Developers Console, as described in Obtain OAuth 2.0 credentials. client_secret
The client secret that you obtain from the Developers Console, as described in Obtain OAuth 2.0 credentials. redirect_uri
The URI that you specify in the Developers Console, as described in Set a redirect URI. grant_type
This field must contain a value of authorization_code
, as defined in the OAuth 2.0 specification.The actual request might look like the following example:
POST /o/oauth2/token HTTP/1.1 Host: accounts.google.com Content-Type: application/x-www-form-urlencoded code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7& client_id=8819981768.apps.googleusercontent.com& client_secret={client_secret}& redirect_uri=https://oauth2-login-demo.appspot.com/code& grant_type=authorization_code
(quoted from stackoverflow)
When your application exchanges the authorization code for an access token, you want to be sure that the OAuth flow which resulted in the authorization code provided was actually initiated by the legitimate user. So, before the client application kicks off the OAuth flow by redirecting the user to the provider, the client application creates a random state value and typically store it in a server-side session. Then, as the user completes the OAuth flow, you check to make sure state value matches the value stored in the user’s server-side session– as that indicates the user had initiated the OAuth flow.
A state value should typically be a pseudo-random unguessable value. A simple value can be generated as an int with the rand() function in PHP, though you could get more complex as well to provide greater assurance.
The state exists to prevent things like me sending you a link via e-mail which contains an authorization code for my account, you clicking on it and the application pushing all the data into my account unbeknownst to you.
Some additional information is in the OAuth 2.0 threat model document: http://tools.ietf.org/html/draft-ietf-oauth-v2-threatmodel-00
In particular, see the section on CSRF protection: http://tools.ietf.org/html/draft-ietf-oauth-v2-26#section-10.12