Some Surprising Things about OAuth 2.0
I recently gave a presentation about using Torii (Ember Auth Library that my company wrote) and doing authentication in Ember. As often happens when I do research before a talk, I went way down the rabbit hole on the related technical topics. This time I spent a lot of time researching OAuth. As I read the RFC closely I realized I had long misunderstood some of the core concepts. I think most people have similar misconceptions, so I wrote this post.
Update: I put together a follow-up post about OAuth 2 and refresh tokens that you should read next.
Some OAuth 2.0 Background
Aaron Parecki’s OAuth 2.0 Simplified is an excellent place to refresh yourself if you’re rusty on the mechanics of OAuth.
OAuth 2.0, like OAuth 1.0, is all about delegating authorization — providing a means by which you, a web visitor, can grant authorization to a client site to access information from another server on your behalf. In the OAuth terminology, these roles are client, server and resource owner. A typical OAuth example is a user (resource owner) who wants to allow a printing site (client) to access her photos at a photo-sharing site (server). Other terms that are sometimes used are consumer (client), service provider (server), and user (resource owner). Here they all are:
- Resource Owner / User: The person who wants to grant authorization to the…
- Client / Consumer: The printing site, which will leverage that authorization grant into the ability to access photos at the…
- Server / Service Provider (or just provider): The photo-sharing site.
If you’re a developer, you are most likely going to be writing the client code here — you’re building a web site that will allow users to connect or log in through some third party like Google+ or Facebook. The resource owner is your end-user, the person who visits the site you’re building. The provider is the third-party that you’re interacting with (Facebook, Github, photo-sharing site, etc.). This post is thus directed at developers: the people writing client code. Client in OAuth parlance is not the same as client in the client-server dichotomy, where it usually means web browser. I agree that this is confusing.
In OAuth, access tokens are the name of the game. As an OAuth client, your primary goal is to obtain an access token. It’s what you use to access information and take actions on behalf of the OAuth user.
You thus want to guide your user through an OAuth flow that allows them to make an authorization grant, which you then turn into an access token. Once you’ve obtained an access token you can use it (usually only for a limited time) to read and sometimes write their data at the provider, depending on what scope of access the user granted.
Types of OAuth flows
There are four authorization flows in the OAuth 2.0 spec. Two of them cover niche cases that don’t apply to most developers. I’ll summarize the two most-common flows here. All flows result in the OAuth client obtaining an access token.
Authorization Code Grant
Authorization Code Grant is the canonical OAuth flow. It involves coordinating browser-based communication (through redirects) and backend server-based communication (from client server to provider server).
- Client crafts a URL from provider’s domain with OAuth-specific information in query parameters (such as client_id, response_type, and redirect_uri) and directs the user’s browser to that URL (either through redirect or by opening a popup). The response_type parameter is required to be “code”.
- Provider displays a consent screen for the user to read (i.e., “Allow application X to read your data?”), the user clicks ok, and the provider then redirects the user’s browser to the redirect_uri with information in the query parameters (including the code parameter, at minimum. If the redirect_uri is http://my-printing-site.com/oauth/callback, for example, the provider might redirect to a url like “http://my-printing-site.com/oauth/callback?code=abc123”.)
- The OAuth client server transmits that authorization code to the provider server, along with information that authenticates this client to the provider (such as the client_key). It’s safe to transmit this secret key since this is backend-server-to-backend-server communication and thus not visible to the user.
- The OAuth provider responds to the client server’s request with the access_token (and sometimes additional information, like a refresh token or details about the expiration time of the access token)
The implicit grant is similar to the authorization code grant but it happens entirely in the user’s browser, no client backend server is involved.
- Client crafts a URL from provider’s domain with OAuth-specific information. Same as in step 1 of Authorization Code Grant flow, except the response_type parameter in this case must be “token”.
- Provider displays consent screen. Same as step 2 of above, but the provider redirects the user’s browser to the redirect_uri with different information. The redirect uri will include the access_token directly (instead of an authorization code) in the redirect, along with some other information. Note that the parameters in this flow are included in the url fragment (the part after the “#” in the URL), not the query string, to avoid leaking this sensitive information in, e.g., a server log file (browsers do not send the url fragment to backend servers when requesting URLs).
Surprising Things about OAuth
Access tokens are opaque
Assuming the same scope is requested in each case, and it’s the same user involved, what’s the difference between access tokens obtained in the following ways?
- Client A gets token from Provider X via the implicit grant flow
- Client A gets token from Provider X via the authorization code grant flow
- Client B gets token from Provider X via the implicit grant flow
- Client B gets token from Provider X via the authorization code grant flow
Here we have two different OAuth clients (e.g. my-printing-site-A and my-printing-site-B) obtaining access tokens from the same provider using each of the two common flows.
Surely the access tokens here are all distinct, right? At the very least, the access tokens that the provider issued to client A must be somehow different from those issued for client B, right?
Wrong. All four of the access tokens described above are effectively interchangeable. They may expire at different rates, but while they are all valid they can be used by anyone and provide the same level of access.
This reminds me a little bit of one of Einstein’s famous thought experiments, the Equivalence Principle, that essentially says that when you are in a box (like a windowless room) you cannot tell through direct observation whether you are on the earth, subject to its gravitational field, or in a rocketship in space, accelerating at exactly the rate of Earth’s gravity.
Similarly, if a malicious hacker managed to break into client A and client B’s databases and randomly switch their 4 stored access tokens described above without detection, neither client A or client B would have any way of figuring out that this had happened. Their code would still be able to use the access tokens in exactly the same way as before the swap.
This is because OAuth access tokens are opaque. They deliberately do not contain any information about how they were issued or to whom (what client) they were. Anyone who possesses an access token can therefore use it, which is why these tokens are also sometimes referred to as Bearer tokens: possession and presenting (“bearing”) the token is all that you need to do to use it. The token represents a user’s authorization decision, and the token is deliberately opaque as to which client originally requested that authorization, because the whole point of OAuth is to be a delegated authorization framework such that one client could securely hand off an access token to another (i.e., “delegate” it). This is the purpose of OAuth.
This interchangeability and lack of client access restriction has important implications for authentication, however. Importantly, your application should not use a token obtained through the implicit grant flow as proof of a user’s authentication. In reality that access token is only proof that the user authenticated themselves to the provider at some point in the past (not necessarily right now), and that they consented for some client (not necessarily yours) to access their information. An access token obtained via the authorization code grant flow is safer to use as an authentication method because you can be more certain when it was issued and that it was issued for your client.
OAuth as a spec/framework (in the 2.0 spec it bills itself as a framework) has an interesting history and is oft-maligned (one of the primary authors of v1.0 and v2.0 famously removed his name from the 2.0 spec, in fact). Like it or not, however, it’s here to stay. As a developer building OAuth clients, it can be very helpful to have a solid understanding of OAuth’s various terms and how its roles interact. Because the spec is so vague, understanding the underlying concepts makes it easier to parse the documentation of OAuth providers. If this post proves helpful I will add a few more OAuth “surprises” to the series.
Update: I’ve written a follow-up post about OAuth 2 and refresh tokens that you should read next.