TL;DR: What are the security implications of using oauth2 for authentication?

I'm building an app (site A) that allows users to perform operations on another website (site B) through a simpler interface.

Site B provides an API that implements OAuth2.0 for authorization of my app.

I was thinking I could avoid storing passwords and having users get yet-another-account at my app by piggybacking on site B's authentication.

Of course I've read the literature bashing on the use of plain OAuth for authentication, but I fail to see how this is bad from a security perspective.

They seem to mostly focus on the practicality and (lack of) generality of the solution.

Here's the scheme I had in mind:

  • User Sally loads site A and clicks "login"
  • She's redirected to site B's authorization page, where she either authenticates or has an active session
  • If she authorizes site A to access site B on her behalf, she is redirected back to site A with an authorization code
  • Site A gets the authorization code and exchanges it for an access token (and a refresh token) through site B's API.
  • Site A asks site B for Sally's user_id and logs her in with that ID
  • The tokens are stored in a database for use by a backend, which does all the real work on site B.

I'll note that I'm using the so-called "server-side flow" here. Also, the authorization_code returned in the third step is a short-lived one-use code that's tied to A's client_id and can only be used with the corresponding client_secret.

When Sally logs in from another device the process repeats and the new tokens are stored.

The only problem I see is that the user will be asked to authorize my app on every login instead of just the first time, but that's not a problem at the moment. I'd also be asking for a new token when I actually have a valid one in store. While a bit impractical, this isn't a problem at the moment(*).

What I'm failing to see is how this is bad from a security perspective.

With such a scheme: What are the security issues for the user? And for the app?

I feel like I'm missing something.

(*) I won't have a big userbase, just a few whitelisted users. When (if) the app grows I plan to integrate it into a bigger site that uses a real OpenID Connect provider. I just want to keep it simple, small and focused during this pilot-test

  • This related SecurityExchange post might help show how using OAuth2 for authentication (rather than authorization) might be an issue. Much of it comes down to whether your app needs to know who the user is, or what that client can/should do (despite who they are). – Castaglia Aug 5 '16 at 16:10
  • @castaglia thanks, I read that and the linked Wikipedia entry. It was actually that answer that led me to ask this question. While useful, it doesn't address any possible security implications. – GnP Aug 5 '16 at 16:38
up vote 21 down vote accepted

Note: If you are looking for something like OAuth2, but for authentication you should
use OpenId Connect instead.


OAuth2 is meant for a user to authorize an application to load the user's resources from some resource provider. In other words: OAuth2 is a mechanism for delegation of authorization. The protocol does not support authentication (although it is commonly misused for exactly that).

The security hole is in the assumption you make in the 5th bullet point.

You say:

Site A asks site B for Sally's user_id and logs her in with that ID

While in reality it should read:

Site A asks site B for the user_id from the user-data that the access_token grants access to.

enter image description here Figure 1: OAuth flow for (confidential) clients.

If all goes as planned, the access_token is, indeed, from the user you redirected to B for authentication. But: there is no guarantee that is the case. In fact, any (malicious) website that the user has previously granted the right to access the user's data (using OAuth2 with B), can get a valid authorization_code from B and send it to you, in bullet point 3.

In other words, if I run a website which asks users for their permission to access their resources at B using OAuth2, I can impersonate all those users at all websites which misuse OAuth2 (with B as OAuth2 Authorization server) for authentication.

The 'problem' with OAuth2 is that the authorization_code is not generated for a specific client_id. So if you receive an authorization_code, you can not be sure if B issued the authorization_code you received to you, or to some other service. Which is deemed acceptable for authorization but is absolutely unacceptable for authentication.

Update:
As to your comment:

(and I restrict A to only accept one from users it previously redirected to B, but this is still unauthenticated)

I believe that you are here adding an extra precaution, which is not mandatory in the OAuth protocol. As such, it cannot be relied upon.

  • 1
    I just saw your latest edits while coming here to write my own answer. Indeed there are implementation details that aren't mandatory in the oauth spec, the most important of which are: authorization_code specific for a client_id, use of state parameter and pre-registered redirect_uri only. All in all, in my specific case I've got everything I need to implement authentication against this provider. Thanks! – GnP Aug 19 '16 at 1:29
  • 1
    So in case authorization_code is validated that belongs to client requesting the refresh token plus using state I see no issue. Am i right? – GorillaApe Oct 8 '16 at 23:28
  • @GorillaApe, No. The only thing the authorization_code means is that the resource owner gave permission to access the resource owner's resources. There are no guarantees otherwise. OAuth2.0 is for authorization, not authentication. If you want Authentication, take a look at OpenID Connect. – Jacco Oct 10 '16 at 9:36
  • 1
    @Jacco. You said OAuth 2 is for authorization, but OAuth 2 has client credentials grant type which can be used for authentication. Any comments – Ashish Rawat Jul 17 '17 at 20:40
  • @AshishRawat, The client credentials grant type is a supported method to authenticate with the Authorization Server in the diagram above. In other words, you authenticate with the Authorization Server, to request delegated Authorization. The fact that there is an authentication step somewhere in the process does not imply in any way or form that it is an that OAuth2 is intended for authentication. If you want authentication, you should use OpenId Connect, which is build on top of OAuth2 and addresses important details. – Jacco Jul 18 '17 at 7:00

As explained by Jacco, a naive implementation of authentication on top of oauth2 has several vulnerabilities, the most common of which is CSRF.

Given there's a perfectly good authentication protocol available without all this pitfalls, it's not a good idea to roll your own.

OTOH, there's a lot to learn by doing it and understanding and fixing these issues.

TL;DR: don't use oauth2 for authentication unless you're doing it to learn why you shouldn't do it. Use OpenID Connect.

OAuth 2.0 Threat Model and Security Considerations

First and foremost, there's an extensive analysis of the threat model for oauth2 in RFC6819

There's several possible "flows" in oauth2. The one I focused on for my project was the authorization_code flow.

Authorization "code"

Here's what RFC6819 has to say about it:

An authorization "code" represents the intermediate result of a successful end-user authorization process and is used by the client to obtain access and refresh tokens. Authorization "codes" are sent to the client's redirect URI instead of tokens for two purposes:

  1. Browser-based flows expose protocol parameters to potential attackers via URI query parameters (HTTP referrer), the browser cache, or log file entries, and could be replayed. In order to reduce this threat, short-lived authorization "codes" are passed instead of tokens and exchanged for tokens over a more secure direct connection between the client and the authorization server.

  2. It is much simpler to authenticate clients during the direct request between the client and the authorization server than in the context of the indirect authorization request. The latter would require digital signatures.

So authorization codes are more secure, yay!

authorization_code flow vulnerabilities are analyzed in section 4.4.1 of RFC6819.

This section covers a lot of ground. I'll just focus on a few of the threats.

CSRF

From section 4.4.1.8:

An attacker could authorize an authorization "code" to their own protected resources on an authorization server. He then aborts the redirect flow back to the client on his device and tricks the victim into executing the redirect back to the client. The client receives the redirect, fetches the token(s) from the authorization server, and associates the victim's client session with the resources accessible using the token.

Impact: The user accesses resources on behalf of the attacker. [...] For example, the user may upload private items to an attacker's resources

This is also covered in section 10.12 of RFC6749:

The client MUST implement CSRF protection for its redirection URI. This is typically accomplished by requiring any request sent to the redirection URI endpoint to include a value that binds the request to the user-agent's authenticated state (e.g., a hash of the session cookie used to authenticate the user-agent). The client SHOULD utilize the "state" request parameter to deliver this value to the authorization server when making an authorization request.

So in your redirect to the oauth2 provider you simply add a parameter state, which is simply a CSRF token (should be unguessable, stored in a secure cookie, etc.). This token will be sent back along with the authorization_code when the oauth2 provider redirects the user back.

The countermeasure for this attack has to be implemented by both the client and the authorization server, and can also be enforced by the authorization server.

The state parameter is also covered in this sec.SE question.

Code Substitution (OAuth Login)

This one (covered in section 4.4.1.13 of RFC6819) is specifically aimed at the authentication over oauth2 scenario.

Basically an attacker obtains an authorization_code for the user through a malicious site (let's call it site C) and sends it to the legitimate site (which we're still calling site A) which exchanges it for an access_token that is then used to assert the user's identity through the resource server. This effectively lets the attacker login as the user on site A.

This is the one mentioned by Jacco in his answer.

The countermeasure for this attack has to be implemented by the authorization server:

All clients must indicate their client ids with every request to exchange an authorization "code" for an access token. The authorization server must validate whether the particular authorization "code" has been issued to the particular client. If possible, the client shall be authenticated beforehand.

Others

Believe it or not, the previous attacks and their countermeasures cover most of the threats to authentication when using the code flow.

There's lots of other threats and countermeasures, many of which should always be implemented:

From section 4.4.1.3:

Handle-based tokens must use high entropy Authenticate the client; this adds another value that the attacker has to guess Bind the authorization "code" to the redirect URI; this adds another value that the attacker has to guess Use short expiry time for tokens

These should all be implemented by the authorization server.

From section 4.1.1.4:

The authorization server should authenticate the client The authorization server should validate the client's redirect URI against the pre-registered redirect URI

These should also be implemented by the authorization server.

From section 4.4.1.5, 4.4.1.6 and others:

the redirect URI of the client should point to an HTTPS protected endpoint

This one should be implemented by the client, and probably enforced by the authorization server.

Then it's okay to use oauth2 for login

Nope. Don't do it. Use OpenID Connect.

Remember the countermeasures from section 4.4.1.13? Well there was another one I didn't quote:

Clients should use an appropriate protocol, such as OpenID (cf. [OPENID]) or SAML (cf. [OASIS.sstc-saml-bindings-1.1]) to implement user login. Both support audience restrictions on clients.

There you go. Use that instead.

If you still want/need to authenticate against an oauth2 provider, first make sure your provider implements all the countermeasures previously mentioned above.

If it does then you may be able to pull it off. Test extensively and hire a security team to perform a full analysis of your solution.

Also, make sure all the provider's features that you rely on for security are documented in you provider's API, otherwise they might be removed without previous notice and you end up with a Very Broken™ product.

In my case: - I was lucky enough that my provider implemented all of these countermeasures on their side. - I'm not relying on this for authentication beyond an initial testing period of the app (it's not a required feature of my app, just a convenient placeholder pre-launch)

Also, I learned enough about oauth2 throughout this implementation to make it well worth it.

If you want to know more, read both RFC6819 and RFC6749. I also found this site very useful.

The security hole in your flow:

She's redirected to site B's authorization page, where she either authenticates or has an active session

If the user has an active session at site B, and other websites (site C, D, etc. ) can use the OAuth system of site B for their authorization, a malicious site ( e.g. site x ) could grab access tokens from site B and potentially use token replay to impersonate the user on your site and carry out actions that were not authorized by the user. Re-read the Facebook example in your links, it explains the problem.

The challenge with the entire scheme is that your site is only going to be as secure as the OAuth API of site B. As we just demonstrated, their API is not secure because third parties can abuse it. In the case of abuse, your only resource is to appeal to site B to ban site x. Personally I would not want to leave the fate of my security in the hands of a site I did not own.

A more secure mechanism would provide authentication of your site against site B's list of accepted OAuth clients, preferably using public key client certificates, and would prevent token replay by limiting token use to the requesting site. You can't force site B to adopt that kind of access scheme, so you are limited to whatever level of security they provide.

  • I think this applies to the "Implicit flow" which I'm not using. – GnP Aug 5 '16 at 20:39
  • The measures you mention in your last paragraph are already inplace, I didn't include them because I thought they were part of the specification. Also, after reading this article under " So what is a Facebook Connect client to do?" I see the three protections are inplace. I've updated the question to reflect this. – GnP Aug 5 '16 at 22:13
  • You should diagram the flow to help yourself and everyone visualize what is going on. As to the auth piece, if you have a TLS client certificate for site A that site B accepts as valid, you should be able to conduct all transactions against site B using the client certificate, which will prevent spoofing from other hosts. The nest question, then, is whether there is a way for user G on your site to impersonate user H by stealing their access token via XSS or something similar. – Robert Munn Aug 7 '16 at 2:25

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.