Let’s imagine I am working on a calendar app. I know that a lot of my users keep their events in Google Calendar, so I need to make sure that I can retrieve their events from Google, and send back the newly created ones. How does Google know that my app is allowed to access a user’s calendar?
As a user, when I go to https://calendar.google.com I’m asked to provide the username and password for my Google Account. So maybe my calendar app should ask the user for their Google Account credentials, and then use those credentials to authenticate with Google. There’s a couple of issues with this approach. Firstly, I need to store the user’s password somewhere. Secondly, those credentials can be used to do much more than access events in Google Calendar. The app – or anyone who has found where the app is storing the password – can read the user’s email, delete their files in Google Drive, or rent some movies from Google Play.
This is where OAuth enters the picture. Instead of asking you for your Google Account credentials, my app asks Google for access to your calendar events. Once Google has confirmed that you are happy, my app is given a token that allows it to access your calendar events and nothing else.
OAuth
OAuth is a collection of open standards that allow applications to be authorized to access resources. The core specification, and various extensions, are developed by the IETF OAuth Working Group.
At the time of writing, there are 2 major versions of the core specification.
OAuth 1.0 was the original specification, released in 2007. A session fixation flaw was discovered in OAuth 1.0, which led to OAuth 1.0a being released in 2009. Finally, OAuth 1.0 was published as RFC 5849 in 2010.
OAuth 2.0 (RFC 6749) is a backward incompatible replacement for OAuth 1.0 designed to cover more use cases, be more extensible, and be simpler for the client developer. It was released in 2012. OAuth 2.1 is currently in draft stage and is intended to consolidate OAuth 2.0 and a number of its extensions into a single specification covering modern use-cases and security best practices.
OAuth 1 is considered obsolete now, so for the rest of this series, assume I am speaking about OAuth 2 unless specified otherwise.
Protocol Overview
OAuth defines four roles:
- Resource Owner
- An entity that can grant access to a resource. In most cases, this essentially means a user.
- Client
- An application that makes resource requests on behalf of the resource owner. This could be a smartphone app, a desktop app, a web app, a smart speaker, etc.
- Resource Server
- The server hosting the resources.
- Authorization Server
- The server issuing access tokens to the client after authenticating the resource owner.
In the early days of OAuth implementations, the authorization server and resource server would often be the same (or at least be operated by the same organization). More recently, with the rise of Okta, Auth0, and similar services, the authorization server tends to be separate and often purchased as a service.
There are four grant types included in the core specification: authorization code, implicit, resource owner password credentials, and client credentials. The protocol flow varies depending on which grant type is in use1, but at a high level, the flow looks like this:
- When the client needs to access a resource, it sends an authorization request to the authorization server. As part of the request, it includes the scope of access that it is asking for.
- The authorization server authenticates the resource owner. This will usually be by presenting a log in page.
- The authorization server gets the resource owner’s consent for access to be granted. This is usually done by presenting a page outlining the scope of access that has been requested, allowing the access to be approved or denied.
- The authorization server provides the client with an access token.
- The client uses the access token to authenticate access to the resource on the resource server.
Scope
Scope is a key concept in OAuth: it defines the access a client is granted to
the resources of a resource owner. Using the calendar example, scope is what
allows the app to access calendar events, without being able to access other
resources in the Google Account. The app would request access with the
https://www.googleapis.com/auth/calendar.events
scope, which allows it to
create, read, update, and delete calendar events. However the app would not be
able to delete a file from Google Drive, as that would require the
https://www.googleapis.com/auth/drive.file
scope.
When performing the authorization request, the client provides a space-separated
list of scopes that are being requested in the scope
parameter of the request.
For example, the following authorization request asks for the read:photos
and
write:photos
scopes2:
https://jammystuff.eu.auth0.com/authorize?
response_type=code&
client_id=p1LJZfMDXrd2EEQp6oV77ASWuN9tlvsK&
redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Fcallback&
scope=read%3Aphotos%20write%3Aphotos&
audience=https%3A%2F%2Fapi.example.com
After the authorization server has authenticated the resource owner, it obtains their consent for the client to access the requested scopes. The resource owner might be given the option to provide their consent for individual scopes, or they may need to accept or decline the entire request. In the screenshot below, the resource owner has to accept or deny the entire request, they cannot allow only read access:
It is also possible for scopes to be denied based on the authorization server’s policy. For example, the authorization server might prevent a user from being able to authorize a scope based on their role, or might prevent a particular client from being authorized for a scope.
When a token is issued to a client, the token is tied to the authorized scopes. This means that the token can only be used to perform actions allowed by the scopes it has been granted.
Tokens
The end result of the authorization flow is that the client will be issued with an access token. Alternatively, they may be issued with a pair of tokens, as there are two types of tokens used within the OAuth protocol:
- Access Token
- An access token is used to authenticate access to a resource.
- Refresh Token
- A refresh token is used to obtain a new access token. For example, this could be because the existing access token expired. Refresh tokens are optional, so an authorization server will not always issue one.
Access Tokens
An access token is a string used by a client to demonstrate to a resource server
that it is authorized to access a resource. The token type is specified in the
token_type
parameter of the token response. This was done to allow for
different token types to be used, and the OAuth RFC even refers to a MAC token
type. However,
it looks like the MAC token type was abandoned and never got past the 5th draft.
In practice, the only token type that really sees any use is the other one referred to in the OAuth RFC: bearer tokens. Put simply, a bearer token is a shared secret. Anyone that knows the secret can use it to authenticate with the resource server.
The OAuth core specification says that access tokens are usually opaque to the client, meaning that their format should not be inferred and the resource server and authentication server need to agree on how to determine their validity and covered scope. Since the release of the core specification, a number of extension specifications have come out of the OAuth working group to provide standards-based approaches to this, including OAuth 2.0 Token Introspection (RFC 7662) and JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens (RFC 9068).
Refresh Tokens
Section 5.1.5.3 of OAuth 2.0 Threat Model and Security Considerations (RFC 6819) suggests that tokens should be given a short expiration time. Most access tokens that you see will have a lifetime of up to a few hours. While this will be fine for some use-cases (such as a printing service accessing your photos to fulfil a print order), other use-cases require longer term access. How do they deal with this?
One option is for the app to simply request authorization again. Authorization servers will usually remember the consent that a resource owner has provided to a particular client, so a follow-up authorization request won’t show the consent page. If the authorization server also offers “Remember Me” functionality, the log in page will not be shown either. Putting this all together, if a client is accessing a resource in response to an interactive request from the resource owner, it is often possible to run through the authorization flow again without any input from the resource owner.
The other option is to use a refresh token. The big advantage of refresh tokens is they do not require the resource owner (or their browser) to be involved in the process at all. Instead, when issuing an access token, an authorization server has the option of also providing the client with a refresh token. Once the access token has expired, the client can use the refresh token to request a new access token directly from the authorization server.
In order to be useful, refresh tokens need to have a longer expiry than access tokens. To mitigate some of the security implications of that, refresh tokens are only used in flows where the tokens are provided directly to the client (i.e. the implicit flow is out!). Additionally, using the refresh token requires the client to authenticate with the authorization server. Finally, refresh tokens are often only single use. If so, a new refresh token will also be provided when a refresh token is used.
While it is often useful to be able to renew access tokens without the resource
owner being present, it does increase the damage that could be done if an
insecure client was breached. It is common for modern authorization server
implementations to only issue refresh tokens if they are explicitly requested
through an offline_access
scope.
Client Types
Although the OAuth specification doesn’t exclude the possibility of using unregistered clients, the advent of extensions such as the OAuth 2.0 Dynamic Client Registration Protocol (RFC 7591) means this is very rarely used, and clients will have been registered with the authorization server. As part of that registration, they indicate their client type. The defined client types are:
- Confidential
- Clients that can keep their client credentials secret, or are able to securely authenticate themselves by some other means.
- Public
- Clients that cannot securely authenticate themselves.
The specification also outlines three client profiles. Most modern implementations have four client profiles, and will have clients register under one of those profiles:
- Web Application
- A “traditional” web application running server-side. These are confidential clients as the client credentials are not exposed to users.
- User-Agent-Based Application
- An application running client side in a user-agent (a web browser). These are public clients, and it is assumed they cannot protect their client credentials or tokens from the resource owner.
- Native Application
- An app installed on a device used by the resource owner (e.g. smartphone apps, desktop apps, etc). While it may initially seem that these would be confidential clients, the resource owner could obtain the client credentials by grabbing them out of memory, and the same credentials will be used on other installs. This means they are considered public clients.
- Machine to Machine Application
- This isn’t defined in the specification, but is called out as a separate client profile in most authorization servers. Machine to machine applications are confidential clients that perform operations on their own behalf rather than a resource owner’s. In other words, they will use the client credentials grant.
All clients are issued with a client identifier. All confidential clients are issued with a client secret, although in practice public clients are usually also issued with one. Authorization servers will often make decisions about grant types that can be used and scopes that can be requested based on the registered client type.
Endpoints
The OAuth specification outlines two endpoints on the authorization server, and one endpoint on the client.
Authorization Endpoint
The authorization endpoint is an authorization server endpoint used to obtain authorization from the resource owner via user-agent redirection. It is only used by the authorization code and implicit grants. The way this endpoint is used depends on the grant type being used, so it will be covered in the posts for each grant type later in the series.
Redirection Endpoint
The redirection endpoint is a client endpoint used to return tokens and errors to the client in response to requests to the authorization endpoint. Essentially, because the authorization endpoint is used by redirecting the resource owner’s user agent to it, the client needs an endpoint that the authorization server can redirect back to with the response.
Token Endpoint
The token endpoint is an authorization server endpoint used to exchange an authorization grant for an access token. It is used by all grant types except the implicit grant. While the details of the request to the endpoint depend on the grant type, the responses are the same in all cases.
Successful Response
If the request is successful, a response will be sent with a 200 OK
status
code and a JSON body with these fields:
- access_token
- The access token that has been issued.
- token_type
- The type of the token that has been issued. As explained in Access
Tokens, this will almost always be
Bearer
. - expires_in
- The lifetime of the access token in seconds. This is optional, but recommended.
- refresh_token
- The refresh token, as explained in Refresh Tokens. This is optional.
- scope
- The scope of the access token. This is optional if the scope is the same as what was requested, but in practice it is usually always included.
Error Response
If the request was unsuccessful, a response will be sent with a 400 Bad Request
status code (unless specified otherwise) and a JSON body with these
fields:
- error
- An error code. The errors are listed below.
- error_description
- A human-readable description providing additional information about the error. This audience of this description is the client developer, not the resource owner. This is optional.
- error_uri
- This is similar to
error_description
, but is a URI to a web page with the information rather than including it in the JSON body itself. It is optional.
The error codes can be one of the following:
- invalid_request
- The request is missing a parameter, repeats a parameter, includes an unsupported value for a parameter, includes multiple credentials or multiple methods of passing credentials, or is otherwise malformed.
- invalid_client
- The client authentication failed. The response must use a
401 Unauthorized
status code if authentication was attempted using theAuthorization
header, and usually that status code will be used regardless of the authentication method. - invalid_grant
- The grant or refresh token is invalid, or invalid for this client.
- unauthorized_client
- The client authenticated successfully, but is not allowed to use this grant type.
- unsupported_grant_type
- The grant type is not supported by the authorization server.
- invalid_scope
- The scope is invalid, malformed, or exceeds the scope granted by the resource owner.
Summary
In this post, I’ve outlined the motivation for OAuth, provided a high-level summary of how the protocol operates, and detailed the foundational concepts that the specification is built on. In the next few posts, I will cover the OAuth grant types in detail, starting with the authorization code grant.
WARNING: This post covered the OAuth 2.0 specification as it was originally written. A number of security best practices have come out of practical experience of deploying OAuth 2.0, which are being collected in the OAuth 2.0 Security Best Current Practice draft. While I plan on covering the content of the draft later in the series, there’s a lot of foundational information to go over first. If you are going to be making use of OAuth before the posts about best current practices, make sure you read that specification yourself!
-
Covering the grant types in detail would make this post very long. The grant types will be covered in later posts in the series. ↩︎
-
You might notice that the
scope
,redirect_uri
, andaudience
fields in the example request look a bit strange. This is because parameters are encoded according to RFC 6749 Appendix B. To explain thescope
value: ‘:’ is encoded as%3A
and the space character is encoded as%20
. ↩︎