The OAuth 2.0 Protocol

The OAuth 2.0 Protocol

2021-12-26T10:21:32.124Z

Overview

How can a user allow an app to access their private data held at a third-party service?

Before OAuth, doing so often required the user to share with the app the user's credentials to the third-party service, allowing the app to "act as the user" when interacting with the third-party service. This practice had a number of issues: since the app needs to store the user's credentials to the third-party service, the app gains excessive power over the user's account, this grant of credentials is only revokable by the user changing their credentials, and the app being compromised endangers the user's credentials and therefore their private data.

Therefore, the problem can be reformulated as: How can a user allow an app access to the user's private data held at a third-party service, without sharing their credentials? And additionally, how can the user limit the access granted to the app?

OAuth ("Open Authorization") solves this problem by introducing the idea of the authorization grant, i.e. the proof that the user has authorized an app to access the user's private data held at a third-party service. This is what that the app presents to the third-party service to prove that the user has authorized the app to access the user's private data held at the third party service.

There are four types of authorization grants:

  • authorization code
  • implicit
  • resource owner password credentials
  • client credentials

OAuth 1.0 was the result of a small ad hoc community effort, whereas the OAuth 2.0 protocol is a later formal spec that is not backward compatible with OAuth 1.0. OAuth 2.0 has become the industry standard.

Authorization code flow

OAuth authorization code flow

Dotted arrows are browser-to-server and server-to-browser requests (front channels, less secure). Solid arrows are server-to-server requests (back channels, more secure).

Step 0: Client app setup

The client is the app being authorized by the user to access their private data held at a third-party service.

The client sets up a client app with the third-party service. During this setup, the client receives a client ID (client identifier) and client secret - these are essentially the client's username and password for interacting with the third-party service.

The client ID is not considered sensitive data; the client secret is. Note that, in OAuth flows where the app is a client-side app (i.e. not backed by a server, the entire source is available to the browser), the client secret is not issued. The client will receive a client secret only if the app is backed by a server, where the secret key can be securely handled.

During this client app setup, the client enters basic information about itself and also registers a redirect URI, also known as callback URL, which is the URL that the user will be redirected to after agreeing to authorize the client. The redirect URI specified during client registration ensures that the authorization server only redirects the user back to this pre-registered redirect URI.

Step 1: Forwarding to prompt

The authorization server is a server controlled by the third-party service. This server will receive the request that initiates the authorization code flow.

When the user clicks on a button at the client, the client makes a request to the authorization server's authorization endpoint, which initiates the authorization code flow.

GET /oauth/authorize
  ? client_id = a17c21eda17c21eda17c21ed
  & response_type = code
  & state = 5ca75bd30
  & redirect_uri = https://client-app.com/callback
  & scope = contacts.read

Host: www.authorization-server.com

This request will specify the following query string parameters:

  • client_id identifies the client app to the third-party service.
  • response_type=code specifies that this is part of an authorization code flow.
  • redirect_uri is self-evident. This URL was pre-registered with the third-party service, but repeating here it decreases the risk of misconfiguration and redirection attacks.
  • scope is the permissions that the client app is requesting, i.e. the actions the client app is asking to be allowed to take on the user's private data.
  • state allows the client app to persist data (e.g. a session key) between the time when the user is redirected to the authorization server, and the time when the user is redirected back to the client. Also used for CSRF protection.

The authorization endpoint takes the user to the third-party service, which asks the user whether they authorize, or not, the client's request for access to the user's private data held at the third-party service.

Step 3: Redirect to callback URL

If the user consents, the authorization server redirects the user to the callback URL, i.e. the user returns to the client. In the URL of this redirect, the authorization server provides the client with an authorization code:

HTTP/1.1 302 Found

Location: https://client-app.com/callback
  ? code = eyJhbGciOiJSUzI1NiIsImtpZCI6ImFmZmM2MjkwN
  & state = 5ca75bd30

code is this flow's authorization grant, i.e. the proof that the user has authorized the client to access the user's private data held at a third-party service. This proof is short-lived - it often expires in 30 to 60 seconds.

Step 4: Exchange of authorization code for access token

On receipt of the authorization code, the client's frontend sends the authorization code to the client's backend using a secure communication channel, e.g. HTTPS. The client's backend, which is trusted to securely handle sensitive information, makes a request to the authorization server's token endpoint, to exchange the authorization code for an access token.

POST /oauth/token
	? grant_type = authorization_code
	& client_id = a17c21eda17c21eda17c21ed
	& client_secret = RjY2NjM5NzA2OWJjuE7c
	& code = eyJhbGciOiJSUzI1NiIsImtpZCI6ImFmZmM2MjkwN

Host: www.authorization-server.com

This requests carries the following query parameters:

  • grant_type again specifies that this request is part of an authorization code flow.
  • client_id and client_secret are those the client received from the third party during setup.
  • code is the authorization code.

If the client ID, client secret and authorization code are all valid, the authorization server responds to the client with an access token:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
{
	"access_token": "e2f8c8e136c73b1e909bb1021b3b4c29",
	"token_type": "Bearer",
	"expires_in": 3920
}

Step 5: Retrieving the user's private data

Finally, on receipt of the access token, the client's backend is able to make an authenticated request to the resource server, i.e. the server where the third-party service holds the user's private data. The client authenticates this request by sending the access token as a Bearer token in the Authorization header.

GET https://api.third-party-service.com/contacts
User-Agent: https://client-app.com
Authorization: Bearer e2f8c8e136c73b1e909bb1021b3b4c29

The resource server receives the request, validates the access token, and performs the action requested by the client on the user's behalf, e.g. allowing read access to the user's contacts:

[
  { "userId": 123, "name": "John Smith" },
  { "userId": 456, "name": "Mary Smith" }
]

Step 6: Refreshing the access token

A token becomes invalid when the access token expires, or when the user has revoked access for the client app, among other reasons. On making a request with an invalid token, the client will receive a 401 Unauthorized response:

HTTP/1.1 401 Unauthorized
{
    "error": "invalid_token",
    "error_description": "The access token expired"
}

Often, a refresh token will also be included in the response from the resource server. If a refresh token is included, the client may request a new access token without restarting the OAuth flow.

{
	"access_token": "e2f8c8e136c73b1e909bb1021b3b4c29",
	"refresh_token": "RjY2NjM5NzA2OWJjuE7cA2OWJjuE7cR",
	"token_type": "Bearer",
	"expires_in": 3920
}

If the access token has expired and a refresh token was provided, the client's backend may make a request to the authorization server's token endpoint:

POST /oauth/token
	? grant_type = refresh_token
	& refresh_token = RjY2NjM5NzA2OWJjuE7cA2OWJjuE7cR
	& client_id = a17c21eda17c21eda17c21ed
	& client_secret = RjY2NjM5NzA2OWJjuE7c

Host: www.authorization-server.com

Instead of authorization_code as in the original request to this endpoint, this time the client specifies grant_type=refresh_token and including the refresh token along with their client ID and client secret.

Do not confuse

The access token is sent to the resource server to obtain the user's private data, but a refresh token is sent to the authorization server to extend the period of validity of the client's access.

The authorization server's response will be a new access token and possibly a new refresh token, much like when exchanging an authorization code for an access token:

HTTP/1.1 200 OK
Content-Type: application/json
{
    "access_token": "BWjcyMzY3ZDhiNmJkNTY",
    "refresh_token": "Srq2NjM5NzA2OWJjuE7c",
    "token_type": "Bearer",
    "expires": 3600
}

If no new refresh token is provided, then the existing refresh token will continue to work once the new access token has expired.

Final note: Why do we exchange an authorization code for an access token?

Why do we go through the trouble of obtaining an authorization code only to exchange it later for an access token? This step exists in order to prevent sensitive data from being exposed the browser. Notice how some of the requests in the authorization code flow are browser-to-server and server-to-browser (dotted arrows in the diagram above) and others are server-to-server (solid arrows).

Less secure requests concern scopes and the authorization code.

  • Browser → authorization server: The client's frontend calls the authorization server's authorization endpoint, specifying scopes and callback URL in query string parameters.
  • Authorization server → browser: The authorization server redirects while providing the client with an authorization code in query string parameters.

More secure requests involve the client secret, the access token and the user's private data.

  • Client server → authorization server: The client's backend sends the client ID, client secret, and authorization code to the authorization server's token endpoint.
  • Authorization server → client server: The client's backend receives the access token from the authorization server.
  • Client server → resource server: The client's backend makes an authenticated request for the user's private data to the resource server.

Implicit grant

In the implicit grant flow, the authorization server responds with the access token immediately, with no authorization code and exchange needed, i.e. via a server-to-browser request. This flow is used for client-side apps with no backend - a static page has no server and so the token is directly exposed to the browser.

In the implicit flow, instead of issuing the client an authorization code, the client is issued an access token directly (as the result of the resource owner authorization). The grant type is implicit, as no intermediate credentials (such as an authorization code) are issued (and later used to obtain an access token). [...] When issuing an access token during the implicit grant flow, the authorization server does not authenticate the client. IETF RFC 6749 pp. 7-8

Resource owner password credentials grant

The resource owner password credentials (i.e., username and password) can be used directly as an authorization grant to obtain an access token. The credentials should only be used when there is a high degree of trust between the resource owner and the client (e.g., the client is part of the device operating system or a highly privileged application), and when other authorization grant types are not available (such as an authorization code). Even though this grant type requires direct client access to the resource owner credentials, the resource owner credentials are used for a single request and are exchanged for an access token. This grant type can eliminate the need for the client to store the resource owner credentials for future use, by exchanging the credentials with a long-lived access token or refresh token. IETF RFC 6749 p. 8

Client credentials grant

The client credentials (or other forms of client authentication) can be used as an authorization grant when the authorization scope is limited to the protected resources under the control of the client, or to protected resources previously arranged with the authorization server. Client credentials are used as an authorization grant typically when the client is acting on its own behalf (the client is also the resource owner) or is requesting access to protected resources based on an authorization previously arranged with the authorization server. IETF RFC 6749 p. 8