nicoCertainly, some of you may be familiar with this situation: our web application needs to be protected with a login. Naturally, we also want to secure the backend services behind it from unauthorized external access and access other APIs – all of this, including single sign-on (SSO) and a rights/roles concept.
“Wait, there was this OAuth and also this JWT, access and refresh tokens, public and confidential clients, authorisation codes, PKCE, client secrets and consent on scopes, IDPs and so on!” Sounds like buzzword bingo? It is: O(Ouch)!
This is where the challenge lies in delving into the topic of OAuth. There are countless terms and concepts, and their definitions and distinctions can be hard to understand. The goal of this blog post is to shed light on this.
To warm up, let’s start with a topic that often leads to confusion:
Authentication vs. Authorization
In this context, you might often come across the abbreviations AuthZ (Authorization) and AuthN (Authentication), which can lead to confusion.
O(Ouch): “What was AuthN and AuthZ again?”
Authentifizierung/AuthN is the process of verifying an identity, for example, by entering a username and password. In the real world, an example would be: You go to the airline counter at the airport with your ID card. The employee checks if your face matches the photo on the ID, thereby verifying your identity to issue you a boarding pass.
Autorisierung/AuthZ is the assignment of rights and privileges to resources. Identity is not a factor here. An example from the real world would be: having a boarding pass that authorizes you to board a plane. Your identity is not rechecked when boarding the plane; only the boarding pass is validated.
Now, let’s clarify whether OAuth is AuthN or AuthZ. The answer can be found in the OAuth standard, The OAuth 2.0 Authorization Framework: It is Authorization!
Great, what does this insight do for us? In short, it helps differentiate the use case of OAuth from other protocols like OpenID Connect (OIDC).
How do OAuth and OIDC differ?
OAuth is an industry-standard protocol that specifies the authorization process for an application to access other protected APIs using access tokens. These access tokens can be likened to the boarding pass at an airport. They authorize access to the API.
A quick note: OAuth tokens should never be used for authentication, such as displaying user information. This is because it can lead to security issues in your application, as OAuth does not provide a standard for secure encoding of user information in access tokens. If you need to store details about the user in the token, you should consider OIDC.
OIDC is based on OAuth 2.0 and extends it to include an authentication view, allowing the display of user information like email address or first and last name. OIDC ensures that a user’s identity is securely verified using the so-called ID tokens. This is done within the context of an active user session.
To help you choose the right protocol, here’s a quick decision guide:
O(Ouch)-Note: There are indeed use cases where the user ID is included in OAuth tokens to allow the called API to provide user-specific data. If only this ID and no other user information (name, e-mail) is used, you are still within the OAuth standard.
Why you should consider OAuth?
The main reason for using OAuth is that it is the industry standard in the area of authorisation. Authorisation of cloud services via OAuth is particularly common in the cloud environment (Azure, AWS, Google, etc.). The OAuth protocol is constantly being improved thanks to a broad user base.
The advantage of standard protocols such as OAuth is that they are described in detail and tested for their effectiveness. Due to their widespread use, a large number of libraries are also available for all common development frameworks. There are also ready-made and customisable solutions such as Keycloak or Auth0, which make our lives easier.
At this point, we strongly recommend that you use these ready-made components and refrain from implementing your own protocol. The risk of an OOuch is very high if you implement it yourself.
Explanation of terms to memorize
The current standard is OAuth 2.0, which is based on the IETF standard The OAuth 2.0 Authorisation Framework from 2012. Over time, a number of extensions have been added, for example on the topics of “PKCE: Proof Key for Code Exchange” or “Security Best Current Practices”. An overview can be found here.
Version 2.1 of the OAuth protocol is currently being developed, which can be described as lessons learnt from the experience of OAuth 2.0.
In the following, we will describe the two most important flows based on our experience at pentacor. Adjustments from OAuth 2.1 will also be applied.
Before we go into the various flows, we would like to explain the most important recurring terms for newcomers and interested parties:
A resource server is a protected API to which access is authorised as part of OAuth. It holds the resources requiring protection that other applications need.
Scope refers to the functionality that the client is authorised to use on the resource server. The authorisation to use the scope is called consent.
The resource owner grants access to protected resources via the resource server. This can be a person, another service or an organisation such as a company.
The client is the application that wants to access protected information from the resource owner. This information is stored on the resource server. With OAuth, a distinction is made between:
- Confidential Clients, e.g. Spring Boot Backends which can securely store client secrets.
The Authorisation Server is responsible for authorising the individual clients by issuing them with an access token. Furthermore, the Authorisation Server is also responsible for authenticating the resource owner, for example in the Authorisation Code Flow.
O(Ouch): “Whaaaat? The Authorization Server is responsible for authentication in OAuth? OAuth is an authorisation framework!”
That’s right. OAuth is an authorisation framework and was developed to authenticate and identify a user. Nevertheless, logging in (authentication) of the user/client is necessary. How does that fit together?
Quite simply: the form of authentication of the resource owner is not specified in the OAuth protocol. This responsibility lies with the Authorisation Server. In practice, it often happens that the Authorisation Server uses a different service for authentication. However, authentication could also be implemented by the Authorisation Server with BasicAuth, for example.
Then there are the tokens that are used for authorisation in the OAuth flows. You should definitely be familiar with the access token. This can be compared to the boarding pass at the airport. There are two different types:
Self-Contained Access Token contain all the information required for authorisation. This means that the authorised scope or the ID of the resource owner are possible components of the access token. A signature is used to ensure the integrity of this type of token.
Opaque Access Token only contain an ID. Details of the authorisation granted must be requested from the Authorisation Server using this ID. One of the advantages of this is that a token can be invalidated before it expires, for example if the resource owner withdraws their consent.
O(Ouch): “I know self-contained tokens, they are those JWTs, aren’t they?”
Basically, that’s true. JWTs are a form of self-contained token. JWTs are primarily used with OIDC and enriched with user information. However, use in OAuth flows without user information in the token is also quite common (see RFC 7523 – JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants).
Refresh tokens are used to retrieve new access tokens from the authorisation server. They offer two main advantages, which are particularly important in the authorisation code flow:
Advantage 1 – Client access without an active user session
Due to the longer validity of the refresh tokens, the client can access the resource server over a longer period of time without the resource owner having to re-authenticate.
Advantage 2 – Increased security due to short access token validity
When using refresh tokens (longer validity), the validity of the access tokens can be very short. This is an advantage as access tokens, which are sent with every request, are potentially easier for an attacker to intercept.
OOuch-Note: As refresh tokens are particularly worth protecting due to their long validity, they should only be stored in confidential clients.
So how does OAuth work now?
OAuth has different flows for different application scenarios. The Client Credentials and Authorisation Code flows are currently common.
The client credential flow is suitable for the problem that an application needs to access another secured API and the authorisation is independent of the user (resource owner).
If my application also needs to access an API and its data on behalf of a user, it is advisable to use the Authorisation Code Flow. In this case, the resource owner (user) explicitly gives their consent for access to their resources.
Here is a little decision-making aid:
In practice, it is often the case with both flows that the consumer application and the called API are developed by different organisational units. Negotiating a security concept between the application and API can be very complex. This is where the strength of the OAuth standard comes into focus. Within the flows, OAuth specifies exactly how secure calls are to be made and what the respective responsibilities are.
Client Credential Flow
Let’s assume we are building a microservice that is supposed to provide us with information about products (ProductService). This service acts as a backend component of a shop system.
Our colleagues kindly provide us with an API that delivers images for a product (ImageService) and uses OAuth with an Opaque Access Token for authorisation.
Great, of course we are happy to use this API.
The Client Credential Flow is ideal for such scenarios (application accesses API without reference to user identity).
This is where our aforementioned protagonists enter the stage:
The ProductService is our Confidential Client and wants to access resources from the ImageService. This means that the ImageService is our resource server. Access from the client to the resource server is authorised by the authorisation server.
Great, now we can get started! All we have to do is register our client with the Authorisation Server (including specifying the requested scopes) and receive our Client ID and Client Secret in return.
Let’s get started!
If the ProductService wants to retrieve image information from the ImageService, it is initially unable to do so as it does not have a valid access token. A request without a valid access token would be acknowledged with an HTTP status 401 (unauthorised).
The steps in the client credential flow are now as follows:
A) Access Token Request
Our ProductService (client) requests an access token from the Authorisation Server. It authenticates itself via client credentials. Optionally, the scope can also be sent in order to specifically request access to the necessary resources.
B) Access Token Response
After verification by the Authorisation Server, it provides an Access Token, with which our client receives proof that it can access the resources of another application (Resource Server) as a Confidential Client.
C) Image Data Request
Now our ProductService requests images from the ImageService (resource server). It sends the Access Token as proof of authorisation.
D) Token Introspection Request
Since we are using an opaque token, the ImageService must ask the Authorisation Server whether the Access Token used is valid and active. This authorisation server endpoint should be secured. In practice, this is done via firewall rules, certificates, API keys or even with client secrets via another client credential flow.
E) Token Introspection Response
After checking the Access Token, the Authorisation Server confirms its validity. In addition, the issued scope and the remaining validity period can also be returned in the response.
F) Image Data Request
Since the authorisation of our ProductService has been confirmed by the Authorisation Server, the ImageService now provides the desired resource for our client.
O(Ouch): “So many requests … you always have to run from A to F …”
A typical problem encountered when implementing an OAuth process is the high number of calls in the system. New tokens are requested and validated for each request, which means that all three components shown have to communicate with each other.
First of all: If the flow A-F is run through for every request to the client, something is going wrong.
This should be prevented by the remaining validity period of the access token being provided by the authorisation server and cached by the client together with the token. As long as the token is still valid, the client uses it to call the resource server. This means that once the Access Token has been issued by the Authorisation Server, only the steps from C to F are run through.
It is even possible to reduce to C to F by using self-contained tokens.
Authorization Code Flow
The authorisation code flow is the method of choice when an application is not allowed to access another service without the explicit authorisation of the user.
An example scenario would be that we develop a web application that enables the user to control smart home devices via the manufacturer’s smart home API. This includes both the direct command (“light on”) by the user and the time control (“switch on Christmas lights every day at 4 pm”) via the application without the user having to log in each time.
What would it take to do this?
First of all, we need an API that we can use to control our smart home products. This is kindly provided to us by the smart home manufacturer and supports authorisation using the OAuth Authorisation Code Flow. Translated into OAuth language, this API is our resource server. The manufacturer also provides us with an Authorisation Server, where the user can later authorise our application to access their smart home devices.
The application we developed consists of a backend client and a frontend client.
O(Ouch): “Why are we using two different clients now??”
The scenario described here could also be implemented without a backend. However, we have decided against this in this example, since the use of a backend has a security advantage. This is due to the fact that the client secret or the access token cannot be stored securely in the front end. It is therefore advisable to store the authentication via client secrets and the tokens in a more secure backend application.
How does the Authorisation Code Flow work?
The user (resource owner) wants to try out our new application. To do this, he or she calls up the URL of the web application and authenticates with a user name and password.
So that the user can now switch on the light, for example, they must allow our application to access their lights via the Smart Home API (resource server). To do this, he selects the manufacturer of his smart lights, authenticates himself with the manufacturer and agrees to be forwarded to the manufacturer’s authorisation server.
B) Redirect to Authorization Server
The actual authorisation code flow begins with this redirect to the manufacturer’s authorisation server. In the query parameters of the redirect, the ID of our application (client ID), the URL of our application (redirect_uri) and the necessary scope are sent to the authorisation server. A possible scope in this example would be “SwitchLampsOnAndOff” and “ListAllLamps”.
C) User Authentication und Consent
Since this is access to a protected resource, the user must also log in to the Authorisation Server.
O(Ouch): “The user doesn’t have to authenticate now, does he?”
Yes, that is absolutely correct. The use of OAuth flows does not exclude authentication. Whether and how authentication takes place is not specified in the OAuth protocol. With the Authorisation Code Flow, user authentication usually takes place on the client and Authorisation Server.
… would be disturbing if everyone could switch our users’ lights on and off, wouldn’t it?
After successful authentication on the authorisation server, the user gives consent for our application (client) to control their smart home devices via the API gateway (resource server). The permitted actions that our application can perform via the API (“Switch lamps on and off” and “List all lamps”) are limited by the scope.
D) Authorization Response
If the user has given their consent, the user’s browser is redirected back to our application via the redirect_uri. A query parameter of the redirect is the so-called authorisation code. This is a code that can later be exchanged for an access token.
This means that we should be able to switch on the light in a moment.
E) Access Token Request
We need an access token so that our application (client) can call up the “Switch on light” action via the resource server (Smart Home API) on behalf of the user (resource owner). The Authorisation Code and the redirect_uri are sent to the Authorisation Server in the so-called Access Token Request. Both parameters are compared by the Authorisation Server with the data from steps C and D. As we are using a confidential client here, our client also sends its client credentials in order to authenticate itself to the authorisation server.
F) Access Token Response
From this point on, the process is similar to the client credentials flow.
After verification by the authorisation server, it returns an access token, which provides our client with proof that it can access the user’s resources in the Smart Home API (resource server) as a confidential client.
G) API Request
Now the backend of our application (Confidential Client) asks the Smart Home API (Resource Server) to switch on the user’s lights. The access token is sent along as proof of authorisation.
H) Token Introspection Request
Here too, the Smart Home API must ask the authorisation server whether the access token used is valid and active. (We also use an opaque token in the example)
I) Token Introspection Response
After checking the access token, the authorisation server confirms its validity. In addition to this information, further details can also be provided. These include, for example, the scope or identification features, such as the customer ID of the smart home provider for which the access token was issued.
J) API Response
After checking the token (H-I), access to the Smart Home API is authorised. 💡
Lights on! After confirmation of the authorisation and internal processing by the resource server, access was granted and our user was able to successfully switch on his light via our application, all within seconds.
(For comparison: the reading time up to this O(ooo)Auth moment was 12 minutes).
So let us state the following:
OAuth is an industry standard protocol that specifies the authorisation process for access from one application to other protected APIs. The access tokens used in OAuth should never be used to authenticate a user. If this is necessary, OIDC should be used. Although a form of authentication also takes place within the OAuth flows, this is independent of the OAuth standard.
Different flows are available for different scenarios, whereby we have presented the two most common ones in this blog post. The Client Credential Flow is suitable for the problem that an application should access another secure API and the authorisation is independent of the user (resource owner).
If my application is to access an API in a different context/organisation on behalf of a user, it is advisable to use the Authorisation Code Flow.
At pentacor, we regularly deal with OAuth in our projects. In some projects this is still relatively simple, as we develop applications that communicate with other APIs via various OAuth flows. We are also no strangers to significantly more complex OAuth problems. For example, in the context of API management, where we deal with questions such as: “How do I register a new API as a resource server on the authorisation server at runtime?” And yes, we can do that too.
Every project has its own challenges and we have already experienced the odd “OOuch, what was that again!” moment.
However, the multitude of possibilities within the OAuth standards, the number of ready-made libraries and, above all, the discussions and lessons learnt with other Pentacor readers ultimately led us to our own O(ooooo)Auth moment 🙂 .