OAuth 2.0 explained

Need to protect an application with tokens? The OAuth 2.0 security framework is what you're looking for. It has flows for web, mobile and IoT clients, plus useful APIs for managing the token lifecycle. What started as a simple and effective solution for granting 3rd party access to social profiles, has evolved to support applications in a range of domains, with even the most stringent security requirements.

1. Token-based security with OAuth 2.0

The token has become a popular pattern for securing applications on the internet. Developers love its conceptual simplicity, architects the ability to design applications with nicely decoupled security roles. However, in the seeming ease of putting together token-based security lurk pitfalls.

To put together a solid framework for dealing with tokens, with sensibly balanced security and ease of use, while drawing on lessons from early success hits (notably the original OAuth), engineers from the internet community devised OAuth 2.0 and published it as RFC 6749 in 2012.

Key aspects in the OAuth 2.0 framework were deliberately kept open-ended and extensible. The base RFC 6749 specifies four security roles and introduces four ways, called authorisation grants, for clients to obtain an access token. The rest, such as what goes inside the token, was left for implementers or future extensions to fill in.

2. The four roles in OAuth

OAuth defines four roles, with clean separation of their concerns. This, together with the shifting of security-related complexity into a dedicated authorisation server, makes it possible to roll out OAuth 2.0 protected applications and services quickly and with consistent security properties.

Resource owner

The end-user. The term reflects OAuth's original purpose, giving 3rd party software access on a user's behalf. Other scenarios are also possible.

Resource server

The protected asset, typically a web API, which requires a token in order to be accessed. The token validation logic aims to be minimal and can also be stateless.

Client

The application -- web, mobile, desktop, or device-based, that needs to obtain a token to access the resource server. The client-side OAuth logic is intended to be simple and minimal.

Authorisation server

Dedicated server for issuing access tokens to the client, after authenticating the end-user and obtaining authorisation, from the end-user or based on some policy.

3. How can a client obtain a token?

In order to obtain an access token the client needs to present a valid grant (credential) to the authorisation server.

If there's one thing that often daunts the newcomer to OAuth 2.0, it's selecting a suitable grant for their application. The choice becomes obvious if you know the type of client (web, mobile, etc) you have.

Here is a rough guide:

Grant type Client type / Use case
Authorisation code Intended for traditional web applications with a backend as well as native (mobile or desktop) applications to take advantage of single sign-on via the system browser.
Implicit Intended for browser-based (JavaScript) applications without a backend.
Password For trusted native clients where the application and the authorisation server belong to the same provider.
Client credentials For clients, such as web services, acting on their own behalf.
Refresh token A special grant to let clients refresh their access token without having to go through the steps of a code or password grant again.
SAML 2.0 bearer Lets a client in possession of a SAML 2.0 assertion (sign-in token) exchange it for an OAuth 2.0 access token.
JWT bearer Lets a client in possession of a JSON Web Token (JWT) assertion from one security domain exchange it for an OAuth 2.0 access token in another domain.
Device For devices without a browser or with constrained input, such as a smart TV, media console, printer, etc.
Token exchange Lets applications and services obtain an access token in delegation and impersonation scenarios.

3.1 Authorisation code flow example

We will now go through an example of a client obtaining an access token from an OAuth 2.0 authorisation server, using the authorisation code grant. This grant is intended primarily for web applications.

The client needs to perform two steps to obtain the token, the first involving the browser, the second a back-channel request. That's why this series of steps is also called the code flow.

Step 1 Step 2
Purpose
  1. Authenticate the user
  2. Obtain the user's authorisation
  1. Authenticate the client (optional)
  2. Exchange code for token(s)
Request Front-channel
(browser redirection)
Back-channel
(client to authorisation server)
Server endpoint Authorisation endpoint Token endpoint
On success Authorisation code
(step 2 input)
Access token
Refresh token (optional)

Code flow: Step 1

The client initiates the code flow by redirecting the browser to the authorisation server with an authorisation request.

Example redirection to the authorisation server:

HTTP/1.1 302 Found
Location: https://c2id.com/login?
 response_type=code
 &scope=myapi-read%20myapi-write
 &client_id=s6BhdRkqt3
 &state=af0ifjsldkj
 &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb

The authorisation request parameters are encoded in the URI query:

  • response_type Set to code to indicate an authorisation code flow.

  • scope Specifies the scope of the requested token. If omitted the authorisation server may assume some default scope.

  • client_id The identifier of the client at the authorisation server. This identifier is assigned when the client is registered with the authorisation server, via the client registration API, a developer console, or some other method.

  • state Optional opaque value set by the client to maintain state between request and callback.

  • redirect_uri The client callback URI for the authorisation response. If omitted the authorisation server will assume the default registered redirection URI for the client.

At the authorisation server, the user will typically be authenticated by checking if they have a valid session (established by a browser cookie), and in the absence of that, by prompting the user to login. After that the server will determine if the client is to be authorised or not, by asking the user for their consent, by applying a rule or policy, or by some other mean.

The authorisation server will then call the client redirect_uri with an authorisation code (on success) or an error code (if access was denied, or some other error occurred).

HTTP/1.1 302 Found
Location: https://client.example.org/cb?
 code=SplxlOBeZQQYbYS6WxSbIA
 &state=af0ifjsldkj

The client must validate the state parameter, and use the code to proceed to the next step - exchanging the code for the access token.

Code flow: Step 2

The authorisation code is an intermediate credential, which encodes the authorisation obtained at step 1. It is therefore opaque to the client and only has meaning to the authorisation server. To retrieve the access token the client must submit the code to the authorisation server, but this time with a direct back-channel request. This is done for two reasons:

  • To authenticate confidential clients with the authorisation server before revealing the token;
  • To deliver the token straight to the client, thus avoid exposing it to the browser.

The code-for-token exchange happens at the token endpoint of the authorisation server:

POST /token HTTP/1.1
Host: c2id.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

grant_type=authorization_code
 &code=SplxlOBeZQQYbYS6WxSbIA
 &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb

The client ID and secret are passed via the Authorization header. Apart from HTTP basic authentication OAuth 2.0 also supports authentication with a JWT, which doesn't expose the client credentials with the token request, has expiration, and thus provides stronger security.

The token request parameters are form-encoded:

  • grant_type Set to authorization_code.

  • code The code obtained from step 1.

  • redirect_uri Repeats the callback URI from step 1.

On success the authorisation server will return a JSON object with the issued access token:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache

{
  "access_token" : "SlAV32hkKG",
  "token_type"   : "Bearer",
  "expires_in"   : 3600,
  "scope"        : "myapi-read myapi-write"
}

The expires_in parameter informs the client for how many seconds the access token will be valid. The scope parameter what powers the token actually has, as some of the originally requested scope values may have been denied or others, not explicitly requested, granted.

The JSON object can include an optional refresh token, which lets the client obtain a new access token from the authorisation server without having to repeat the code flow.

4. How can a client access a protected resource with a token?

Accessing a resource server, such as a web API, with a token is super easy. The access tokens in OAuth 2.0 are commonly of type bearer, meaning the client just needs to pass the token with each request. The HTTP Authorization header is the recommended method.

GET /resource/v1 HTTP/1.1
Host: api.example.com
Authorization: Bearer oab3thieWohyai0eoxibaequ0Owae9oh

If the resource server determines the token to be valid and not expired, it will proceed with servicing the request.

Else, it will return an appropriate error in a HTTP WWW-Authenticate response header.

Example error for an invalid or expired token:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api.example.com",
                         error="invalid_token",
                         error_description="The access token is invalid or expired"

If the client receives an invalid_token error for an access token that used to work, this is a signal that it must request a new one from the authorisation server.

The usage of bearer tokens is specified in RFC 6750.

Keep in mind that the bearer nature of the token means whoever has possession of it can access the resource server. The bearer tokens must therefore be protected:

  • First, by keeping the tokens in safe client-side storage.
  • By submitting the tokens over TLS / HTTPS.

Issuing short-lived tokens can mitigate the potential risk of token leakage.

To fix the bearer weakness of access tokens authorisation servers and clients can implement the mTLS extension (for web and native applications) or the dPOP extension (for SPAs) which bind the token to a private key owned by the client. When an access token is bound to a private key, which can be kept in a secure HSM or device storage, the access token is unusable without the key. Applications that need higher security, such as in OpenBanking, therefore require mTLS secured tokens.

5. How to secure a resource server with tokens?

To clear a submitted request the resource server needs to validate the token. The validation ensures the following:

  1. That the access token has been issued by the authorisation server.
  2. That the token hasn't expired.
  3. Its scope covers the request.

How that's done depends on the particular implementation of the access token, which is determined by the authorisation server. More on that in the next chapter.

The required token validation logic can be easily abstracted from the underlying resource, or retrofitted to an existing one. A reverse HTTP proxy or an API gateway can be utilised to achieve that.

Most popular web servers and frameworks, such as Apache httpd and Spring, provide some sort of module for validating incoming requests secured with a bearer access token. Upon successful validation the underlying resource can be provided with selected useful token attributes, such as the end-user's identity. This again is a matter of implementation.

6. The access token

The OAuth 2.0 framework doesn't prescribe a particular embodiment for access tokens. This decision is left to implementors for a good reason: to clients the token is just an opaque string; their validation is a matter between the authorisation server and the resource server(s), and since they usually belong to the same provider there hasn't been enough demand to specify a standard token format.

Fundamentally, there are two types of bearer token:

Identifier-based Self-contained
The token represents a hard-to-guess string which is a key to a record in the authorisation server's database. A resource server validates such a token by making a call to the authorisation server's introspection endpoint. The token encodes the entire authorisation in itself and is cryptographically protected against tampering. JSON Web Token (JWT) has become the defacto standard for self-contained tokens.

Pros / cons of identifier-based tokens:

  • If needed, a client's or user's access tokens can be revoked with immediate effect.
  • Token validation requires a network call which may increase the time to serve requests.
  • May be difficult to scale with distributed applications.

Pros / cons of self-contained tokens:

  • Tokens can be validated on the spot, by checking their signature and then extracting their payload.
  • Easy to scale with distributed applications.
  • The revocation of access takes effect when the issued tokens expire.

7. The authorisation server

The authorisation server is responsible for issuing access tokens for protected resources under its control.

OAuth gives server implementers the freedom to determine how the users get authenticated and how the actual authorisation is obtained. The Connect2id server takes full advantage of this freedom and provides a flexible web API for plugging arbitrary authentication factors, policies and logic to determine the issue of the tokens and their properties, such as scope.

7.1 Authenticating users

As with the user authentication, OAuth lets server implementers decide how the user authorisation is obtained and what scope and other properties the issued access tokens receive.

Authorisation servers that issue tokens for accessing personal data and resources typically render a consent form where the user can decide which scopes to grant to the requesting application.

An enterprise authorisation server would typically consult a policy which takes into account the person's status, role and entitlements to determine the token scope. The client's trust level, e.g. internal application vs external application, can also be a factor in the authorisation.

7.2 Authorisation

As with end-user authentication, the process of authorisation and determining what scope the issued tokens receive is implementation specific.

Authorisation servers that issue tokens for accessing end-user data and other resources typically render a consent form where the end-user can decide which scopes to grant and which not to the requesting application.

An authorisation server in a company would consult a policy which takes into account the employee's status and role to determine the token scope, skipping any consent-related interaction. The client's trust level, e.g. internal application vs external application, can also be a factor in this.

7.3 Client authentication

OAuth defines two types of clients, depending on their capability to authenticate securely with the authorisation server:

  • Confidential -- A client with credentials which uniquely identify it with the server and which are kept confidential from the user and other entities. A web application which executes on a web server can be such a client.

  • Public -- A clients without credentials. An application which executes on a mobile device or in a browser (SPA) can be such a client.

Regardless of their type, all clients get a unique client_id assigned by the authorisation server.

Six different methods for authenticating clients have been specified so far and an authorisation server can implement any one of them:

Method Description
client_secret_basic Essentially HTTP basic authentication with a shared secret. The most widely implemented because of its simplicity, but also with the weakest security properties.
client_secret_post A variant of basic authentication where the credentials are passed as form parameters instead of in the Authorization header.
client_secret_jwt The client authenticates with a JSON Web Token (JWT) secured with an HMAC using the client secret as key. Prevents leakage of the secret and limits the time-window for replay if the request is accidentally sent over unsecured HTTP.
private_key_jwt Another JWT-based authentication method, but using a private key, such as RSA or EC, which public part is registered with the authorisation server. Private key authentication limits the proliferation of secrets and also provides a non-repudiation property.
tls_client_auth The client authenticates with a client X.509 certificate issued from a CA authority trusted by the authorisation server.
self_signed_tls_client_auth The client authenticates with a self-signed client X.509 certificate. Has similar properties as the private key JWT method.

7.4. Authorisation server endpoints

Core endpoints Optional endpoints

7.4.1 Authorisation endpoint

This is the server endpoint where the end-user is authenticated and authorisation is granted to the requesting client in the authorisation code and implicit flows (grants). The remaining OAuth 2.0 grants don't involve this endpoint, but the token enpoint.

This is also the only standard endpoint that serves requests via the front-channel (the browser) and where users interact with the server.

7.4.2 Token endpoint

The token endpoint lets the client exchange a valid grant, such as a code obtained from the authorisation endpoint, for an access token.

A refresh token may also be issued, to allow the client to obtain a new access token when it expires without having to resubmit a new instance of the original authorisation grant, such as code or the resource owner password credentials. The intent of this is to minimise the amount of end-user interaction with the authorisation server and thus better the overall experience.

If the client is confidential it will be required to authenticate at the token endpoint.

7.4.3 Optional endpoints

Authorisation servers can have these additional endpoints:

Authorisation servers can have these additional endpoints:

  • Server metadata -- JSON document listing the server endpoint URLs and its OAuth 2.0 features and capabilities. Clients can use this information to configure their requests to the server.

  • Server JWK set -- JSON document with the server's public keys (typically RSA or EC) in JSON Web Key (JWK) format. These keys may be used to secure issued JWT-encoded access tokens and other objects.

  • Client registration -- RESTful web API for registering clients with the authorisation server. Registration may be protected (require pre-authorisation) or open (public). The endpoint can be enhanced to support client read, update and delete operations.

  • Pushed authorisation request (PAR) -- enables clients to POST the payload of an authorisation request directly to the server, the resulting handle can then be used at the authorisation endpoint to complete the request. With PAR clients can be authenticated upfront (before the user interaction) and the authorisation request parameters receive integrity and confidentiality protection.

  • Token introspection -- For letting resource servers validate identifier-based access tokens. May also be used to validate self-contained access tokens.

  • Token revocation -- For letting clients notify the authorisation server that a previously obtained refresh or access token is no longer needed. Note this endpoint isn't intended for revoking access for an end-user or client; that will require a custom API.

8. FAQ

8.1 How is OpenID Connect related to OAuth 2.0?

OpenID Connect is a concrete protocol for authenticating users, devised on top of the OAuth 2.0 framework. As such OpenID Connect is also often called a profile of OAuth 2.0.

Do not use plain OAuth to autenticate users! OpenID Connect is designed specifically for this purpose.

OpenID Connect introduces a new token, called ID token, to assert the user's identity and the authenetication event.

The access token is still used. It facilitates retrieval of consented profile details (called claims or attributes) from the UserInfo endpoint of the OpenID provider.

8.2 What other OAuth 2.0 profiles exist?

The community around the OpenID Foundation is developing other concrete profiles besides OpenID Connect, carefully vetted by experts, to provide token-based security for a particular industry or application domain:

  • FAPI -- High-security profile for financial-grade applications, used in Open Banking and PSD2 implementations.

  • HEART -- Profile for letting individuals control access to their health-related data.

  • MODRNA -- For mobile network operators providing identity services to relying parties.

  • EAP -- Security and privacy profile integrating token binding and FIDO.

  • iGOV -- For authenticating and sharing consented data with public sector services across the globe.


comments powered by Disqus