The OAuth Protocol

The OAuth Protocol

2021-12-26T10:21:32.124Z

Source:

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

Before OAuth, a common pattern was for the user to share with the app the user's credentials to the third-party service, allowing the app to act as the user with respect to the third-party service.

This pattern presented a number of problems and limitations:

  • The app needs to store the user's credentials to the third-party service.
  • The app needs to support password authentication despite its security weaknesses.
  • The app gains full almost permanent power over the user's account.
  • The user can only revoke the app's access by changing the user's credentials.
  • The app becoming compromised endangers the user's credentials and protected data.

Therefore, the problem can be reformulated as: How can a user allow an app limited access to protected user data at a third-party service, without sharing user credentials?

To solve this problem, the OAuth protocol introduces the idea of authorization grant, a credential representing the resource owner's authorization to access their protected resources. Four authorization grant types are defined:

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

The purpose is to allow for a flow such as:

For example, an end-user (resource owner) can grant a printing service (client) access to her protected photos stored at a photo-sharing service (resource server), without sharing her username and password with the printing service. Instead, she authenticates directly with a server trusted by the photo-sharing service (authorization server), which issues the printing service delegation-specific credentials (access token).
— IETF RFC 6749 p. 4

OAuth 1.0 vs. OAuth 2.0

OAuth 1.0 was the result of a small ad hoc community effort - the OAuth 2.0 protocol is a new formal spec not backward compatible with OAuth 1.0. The two versions may co-exist on the network, and implementations may choose to support both, but OAuth 2.0 has become the industry standard

Terminology

To understand the OAuth flow, we must first learn the terminology.

  • Authentication vs. authorization

    • Authentication means verifying identity - an app that authenticates is only verifying who the user is.
    • Authorization means verifying permissions - an app that authorizes is only verifying if a user is entitled to perform an action on a resource.
  • Roles

    • A resource owner is an entity capable of granting access to a protected resource - the entity (usually, a user) who owns protected data held at a third-party service and who is entitled to consent to it being shared.
    • A resource server is the third party's server holding the protected user data.
    • A client is an app that makes protected resource requests on behalf and with authorization of the resource owner - the web, desktop or mobile app that intends to access the user's data at a third-party service with the user's consent. A client is classified based on their ability to authenticate securely with the authorization server: a confidential client is capable of keeping their credentials confidential (e.g. a web app, one having a secure backend); a public client is incapable of this (e.g. a browser-based app or mobile app). Client type determines if the client will be entrusted with a client secret or not.
    • An authorization server is the third party's server that will prompt the user for consent to share, hand the client an authorization grant, and issue provide access tokens to allow access to the user's protected data. (Sometimes the resource server and authorization server can be one and the same.)
  • Data exchanged

    • Scopes: Scopes are granular permissions to interact with the user's data at the third-party service, e.g. contacts.read, contacts.write, email.read, email.delete. The scopes that the client requests are used by the authorization server to generate the prompt presented to the user, e.g. "This service is asking for permission to read your contacts. Do you want to allow this?" The client's access to user data is always scoped.
    • Redirect URI or callback URL: The client's URL that the user will be redirected to after consenting to share their data with the client.
    • Authorization code: The code that the authorization server provides to the client during the redirect to the callback URL after the resource owner has consented.
    • Access token: Credentials used to access protected resources - a string representing an authorization issued to the client in return for an authorization code. Access tokens represent specific scopes and durations of access, granted by the resource owner, and enforced by the resource server and authorization server.
  • Endpoints:

    • Authorization endpoint: Endpoint at the authorization server for the client to obtain an authorization grant from the resource owner.
    • Token endpoint: Endpoint at the authorization server for the client to exchange an authorization grant for an access token.
    • Redirection endpoint: Endpoint at the client server for the authorization server to return responses containing authorization credentials to the client.

While originally designed as an authorization protocol, OAuth has come to be used for authentication as well, e.g. Google and Facebook use OAuth for identifying a user at a third-party app.

General OAuth flow description

General (grant-agnostic) version of the OAuth 2.0 protocol flow:

OAuth authorization code flow

The abstract OAuth 2.0 flow illustrated in Figure 1 describes the interaction between the four roles and includes the following steps:
(A) The client requests authorization from the resource owner. The authorization request can be made directly to the resource owner (as shown), or preferably indirectly via the authorization server as an intermediary.
(B) The client receives an authorization grant, which is a credential representing the resource owner's authorization, expressed using one of four grant types defined in this specification or using an extension grant type. The authorization grant type depends on the method used by the client to request authorization and the types supported by the authorization server.
(C) The client requests an access token by authenticating with the authorization server and presenting the authorization grant.
(D) The authorization server authenticates the client and validates the authorization grant, and if valid, issues an access token.
(E) The client requests the protected resource from the resource server and authenticates by presenting the access token.
(F) The resource server validates the access token, and if valid, serves the request.
— IETF RFC 6749 pp. 6-7

Authorization code flow description

As for the authorization code flow, at a high level it follows these steps:

Step 0: Client app setup — The client registers a client app at the third-party service.

Step 1: Redirect to prompt — The user performs an action at the client app, most commonly clicking a button, which initiates the OAuth flow and redirects the user to the third-party service.

Step 2: Prompt for consent — The third-party service prompts the user to allow or deny the client's request for scoped access to the user's data at the third-party service.

Step 3: Redirect to callback URL — The user consents and is redirected to a specific URL at the client's app. During this redirect, the client receives an authorization code from the authorization server.

Step 4: Exchange for access token — The client immediately makes a request to the authorization server, exchanging the authorization code for an access token.

Step 5: Request for user data — The client makes another request, this time with the access token and to the resource server, which responds with the user's data.

OAuth authorization code flow

The OAuth authorization code flow. Black arrows indicate front channels; orange arrows indicate back channels

Why do we exchange an authorization code for an access token?

For network security reasons. This exchange step exists in order to keep sensitive data from being exposed to the browser. The authorization code flow is designed to take advantage of the strengths of two kinds of channels: some requests along the authorization code flow are browser-to-server (front channel, less secure) and other requests are server-to-server (back channel, more secure).

  • At step 1, the client specifies the callback URL and the requested scopes to the authorization server through the query parameters of a browser-to-server GET request (front channel).
  • At step 3, the authorization server provides the client with an authorization code through the query parameters of the URL to which the user is redirected (front channel).
  • At step 4, the client's server sends the authorization code, together with the client's credentials with respect to the third-party service, to the authorization server, in the body of a server-to-server POST request (back channel). The client's server also receives the authorization server's access token through that same channel. The access token and the client's credentials are never exposed to the browser.
  • At step 5, the client's server requests the user's data with the access token in a server-to-server request (back channel). The access token and the client's credentials are never exposed to the browser.

Authorization code flow as described by the spec:

OAuth authorization code flow

(A) The client initiates the flow by directing the resource owner's user-agent to the authorization endpoint. The client includes its client identifier, requested scope, local state, and a redirection URI to which the authorization server will send the user-agent back once access is granted (or denied).
(B) The authorization server authenticates the resource owner (via the user-agent) and establishes whether the resource owner grants or denies the client's access request.
(C) Assuming the resource owner grants access, the authorization server redirects the user-agent back to the client using the redirection URI provided earlier (in the request or during client registration). The redirection URI includes an authorization code and any local state provided by the client earlier.
(D) The client requests an access token from the authorization server's token endpoint by including the authorization code received in the previous step. When making the request, the client authenticates with the authorization server. The client includes the redirection URI used to obtain the authorization code for verification.
(E) The authorization server authenticates the client, validates the authorization code, and ensures that the redirection URI received matches the URI used to redirect the client in step (C). If valid, the authorization server responds back with an access token and, optionally, a refresh token.
— IETF RFC 6749 p. 24

Authorization code flow implementation

Step 0: Client app setup

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, which are essentially the client's username and password with respect to the third-party service. The client ID is not considered sensitive data; the client secret is. The client secret may or may not be issued - during setup, the developer is asked about client type if their app is a web-server app or a client-side app (i.e. public or confidential), and receives a secret key only if the app is backed by a server where the secret key can be securely handled.

As part of this setup with the third-party service, the client enters basic information about the app and also registers a redirect URI. Registration of the redirect URI is required to prevent malicious redirects and HTTPS is required for the redirect URL to prevent interception of the authorization code. Per the OAuth spec, only apps running on the loopback interface, e.g. native desktop apps or apps in local development, are exempted from the HTTPS requirement, but even so some OAuth services may still require HTTPS for the redirect URI.

Step 1: Redirect to prompt

When the user performs an action at the client app, most commonly clicking a button, the client makes the following request to the authorization server, at the authorization endpoint:

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

The client ID identifies the client app to the third-party service. response_type=code specifies that this request initiates an authorization code flow. redirect_uri is the client's URL that the user will be redirected to after consenting, provided that this redirect URI matches the one registered by the client during setup. scope is the permissions that the client app is requesting. state allows the client app to persist data (e.g. a session key) between when the user is redirected to the authorization server and when the user is redirected to the client's callback URL; state is also used for CSRF protection.

At the authorization endpoint, the third-party service prompts the user to allow or deny the client app scoped access to the user's data at the third-party service.

Step 3: Redirect to callback URL

Once the user consents, the authorization server redirects the user to the client's callback URL.

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

This code in the query parameters to the redirect URI is the authorization code provided by the authorization server to the client, in compliance with the query parameter response_type=code in the client's first request. The authorization code is a temporary code that the client will exchange for an access token. The authorization code expires shortly after issuance - the spec recommends at most 10 minutes, but most services set it around 30 to 60 seconds.

Step 4: Exchange for access token

Having received the authorization code, the client's server immediately makes a request to the authorization server's token endpoint, to exchange the authorization code for an access token:

POST /oauth/token HTTP/1.1
Host: www.authorization-server.com
Content-Type: application/json

grant_type = authorization_code
& client_id = a17c21eda17c21eda17c21ed
& client_secret = RjY2NjM5NzA2OWJjuE7c
& code = eyJhbGciOiJSUzI1NiIsImtpZCI6ImFmZmM2MjkwN

code is the authorization code. client_id and client_secret are those the client received from the third party during setup (often encoded in a Basic header). grant_type again specifies that this request is part of an authorization code flow.

If the authorization code, client ID, and client secret are all valid, the authorization server responds to the client app 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: Request for user data

Having received the access token, the client is able to make an authenticated request to the third party's resource server, 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 finally performs the scoped action requested by the client on the user's behalf.

Extra step: Refresh token

The authorization server provided the client with an access token that expires in the specified number of seconds. Often, a refresh token will also be included in the response. If a refresh token is included, the client can request a new access token without the user's interaction. A refresh token is long-lasting credential used to request additional access tokens, so the refresh token is bound to the client to which it was issued.

HTTP/1.1 200 OK
Content-Type: application/json
{
	"access_token": "e2f8c8e136c73b1e909bb1021b3b4c29",
	"refresh_token": "RjY2NjM5NzA2OWJjuE7cA2OWJjuE7cR",
	"token_type": "Bearer",
	"expires_in": 3920
}

Unlike an access token, a refresh token is intended to be sent to an authorization server - a refresh token is never sent to a resource server.

Tokens can expire for many reasons: the specified number of seconds has elapsed, the user has revoked access for the client app, the user has changed their password, etc. If the client attempts a request with a invalid token, the client will receive a 401 Unauthorized response:

HTTP/1.1 401 Unauthorized
Content-Type: application/json
{
    "error": "invalid_token",
    "error_description": "The access token expired"
}

In this case, the client will need to either restart the OAuth flow to prompt the user for consent again, resulting in a new access token, or use the refresh token if they have one. In the latter case, the client makes a request to the authorization server's token endpoint specifying grant_type=refresh_token and including the refresh token and the client credentials if required.

POST /oauth/token HTTP/1.1
Host: authorization-server.com

grant_type = refresh_token
& refresh_token = RjY2NjM5NzA2OWJjuE7cA2OWJjuE7cR
& client_id = a17c21eda17c21eda17c21ed
& client_secret = RjY2NjM5NzA2OWJjuE7c

The response will be a new access token and possibly a new refresh token, much like those provided when initially exchanging the authorization code for the first access token. If no new refresh token is provided, then the existing refresh token will continue to work once the new access token has expired.

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

expires_in refers to the access token, not to the refresh token - the expiration time of the refresh token is never communicated to the client.


Refresh token flow as described in the spec:

The flow illustrated in Figure 2 includes the following steps:
(A) The client requests an access token by authenticating with the authorization server and presenting an authorization grant.
(B) The authorization server authenticates the client and validates the authorization grant, and if valid, issues an access token and a refresh token.
(C) The client makes a protected resource request to the resource server by presenting the access token.
(D) The resource server validates the access token, and if valid, serves the request.
(E) Steps (C) and (D) repeat until the access token expires. If the client knows the access token expired, it skips to step (G); otherwise, it makes another protected resource request.
(F) Since the access token is invalid, the resource server returns an invalid token error.
(G) The client requests a new access token by authenticating with the authorization server and presenting the refresh token. The client authentication requirements are based on the client type and on the authorization server policies.
(H) The authorization server authenticates the client and validates the refresh token, and if valid, issues a new access token (and, optionally, a new refresh token).
— IETF RFC 6749 pp. 10-11

Other authorization grant types

Implicit grant

The implicit grant skips the exchange of authorization code for access token. In the implicit flow, the authorization server responds with the access token immediately, with no authorization code and exchange needed, and exclusively through the front channel. This is used when there is no back channel, e.g. a client-side app with no backend - a static page has no back channel 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