From Spring Security in Action by Laurentiu Spilca
In this article, we discuss the components which act in an OAuth 2 authentication implementation. You need to know these components and the role they play in order to properly implement them.
The components of the OAuth 2 authentication architecture
In this article, we only discuss what these components are, and their purpose (figure 1).
Figure 1. The main components of the OAuth 2 architecture are the resource owner, the Client, the authorization server, and the resource server. Each of them has its own responsibility, essential in the authentication and authorization process.
- The resource server – The application hosting resources owned by users. Resources can be users’ data or actions they are authorized to do.
- The user (also known as the resource owner) – This is the individual who owns resources exposed by the resource server. A user generally has a username and a password which they use to identify themselves.
- The Client – an application that accesses the resources owned by the user, on behalf of the user. The Client uses a client ID and a client secret to identify itself. Be careful, these credentials are not the same as the user’s credentials. The Client needs its own credentials to identify itself when making a request.
- The authorization server – the application which authorizes the Client to access the users’ resources exposed by the resource server. When the authorization server decides that a client is authorized to access a resource on behalf of the user, it issues a token. The Client uses this token to prove to the resource server that it was authorized by the authorization server. The resource server allows the Client to access the resource they requested if the Client has a valid token.
Implementation choices with OAuth 2
In this section, we discuss how to apply OAuth 2, depending on the architecture of your application. As you’ll learn, OAuth 2 implies multiple possible authentication flows, and you need to know which one applies to your case. I’ll take the most common cases and evaluate them. It’s important to do this before starting with the first implementation to know what you’re implementing.
How does OAuth 2 work then? What does it mean to implement OAuth 2 authentication and authorization?
Mainly, OAuth 2 refers to using tokens for authorization. Tokens are like access cards. Once you obtain a token, you can access specific resources, but OAuth 2 offers multiple possibilities for obtaining the token. We call these ways to obtain the token grants.
Here are the most common OAuth 2 grants you can choose from:
- Authorization Code
- Refresh Token
- Client Credentials
When starting an implementation, we need to choose our grant. Do we select it randomly? No, we need to know how tokens are created in each of the grant types. Then, depending on our application requirements, we choose one of them. Let’s analyze each one of them shortly, and where it applies. You’ll also find an excellent discussion about grant types in section 6.1 of OAuth 2 In Action by Justin Richer and Antonio Sanso (Manning, 2017).
Implementing the authorization code grant type
In this section, we discuss the Authorization Code grant type (figure 2). This grant type is one of the most used OAuth 2 flows, and it’s quite important to understand how it works and how to apply it. A high probability exists that you’ll use it in the applications you develop.
Figure 2. The authorization code grant type. The Client asks the user to interact directly with the Authorization Server to grant them permission for the user’s request. Once authorized, the Authorization Server issues a token that the Client uses to access the user resources.
Here’s how the authorization code grant type works step by step:
- Making the authentication request.
- Obtaining the access token
- Calling the protected resource
Step 1: Making the authentication request with the authorization code grant type
The Client redirects the user to an endpoint of the authorization server where they need to authenticate. You can imagine that you’re using app X, and you need to access a protected resource. To access that resource for you, app X needs you to authenticate. It opens a page for you with a login form on the authorization server where you must fill in your credentials.
What technically happens here is that, when the Client redirects the user to the authorization server, the Client calls the authorization endpoint with the following details in the request query:
- response_type with the value “code” tells the authorization server that the Client expects a code. The Client needs the code to obtain an access token, as you’ll see in the second step.
- client_id with the value of the client ID, which identifies the application itself.
- redirect_uri – tells the authorization server where to redirect back the user after successful authentication. Sometimes the authorization server already knows for each Client a default redirect URI. For this reason, you’ll see the Client doesn’t send the redirect URI anymore.
- scope – these are the granted authorities.
- state – a Cross-Site Request Forgery (CSRF) token, used for CSRF protection.
After successful authentication, the authorization server calls back the Client on the redirect URI and provides a code and the state value. The client checks that the state value is the same as the one it sent in the request to confirm that wasn’t someone else to call the redirect URI.
The Client uses the code to obtain an access token, as presented in step 2.
Step 2: Obtaining the access token with the authorization code grant type
The code resulted from step 1 is the Client’s proof that the user authenticated to allow them to access resources. You guessed correctly, this is why this grant type is called “authorization code.” Now the Client calls the authorization server with the code they have to get the token.
Figure 3. The first step implies direct interaction between the user and the Authorization Server. In the second step, the Client requests an access token from the Authorization Server providing the authorization code obtained previously in step 1.
In many cases, these first two steps create confusion. People are generally confused about why the flow needs two calls to the authorization server and two different “tokens”: the authorization code and the access token. Take a moment to understand this.
- The first code is generated as proof that the user directly interacted with the authorization server and successfully authenticated to allow grants to the Client.
- The second token is the key that the Client can use to access resources on the resource server.
Why didn’t the authorization server directly return the second token (access token) then? Well, OAuth 2 defines a flow called the “implicit grant type” where the authorization server directly returns the access token. The implicit grant type isn’t enumerated because it’s not recommended that you use it, and most of the authorization servers today don’t allow using it. The simple fact that the authorization server calls the redirect URI directly with the access token without making sure that it was the right Client receiving that token makes the flow less secure. By sending an authorization code first, the Client has to prove again who they are by using their credentials to obtain the access token. The Client makes a final call to get the access token sending
- the authorization code (which proves the user authorized them)
- their credentials (to prove they’re the correct Client and not someone else who intercepted the authorization codes)
To return to step 2 – technically – the Client now makes a request to the authorization server containing:
- code – authorization code received in step 1, which proves the user authenticated.
- client_id and client_secret – which are the credentials of the Client.
- redirect_uri – should be the same as the one used in step 1 for validation.
- grant_type – needs to have the value “authorization_code” and identifies the kind of flow used. A server might support multiple flows, and it’s essential always to specify which is the current executed authentication flow.
As a response, the server sends back the
access_token is a value that the Client can use to call resources exposed by the resource server.
Step 3: Calling the protected resource with the authorization code grant type
The Client uses the access token in the
Authorization request header when calling an endpoint of the resource server.
An analogy for the authorization code grant type
I’ll end this section with an analogy to this flow. I sometimes buy books from a small shop I’ve known for ages. I order the books in advance and then pick them up a couple of days later, but the shop isn’t on my daily route, and I can’t collect the books myself. I usually ask a friend who lives near me to go there and collect them. When my friend asks for my order, the lady from the shop calls me to confirm I’ve sent someone to collect my books. After I confirm, my friend collects the package and brings it to me later in the evening.
In this analogy, the books are the resources. I own them, and I’m the user (resource owner). My friend who picks them up for me is the Client. The lady selling the books is the authorization server (we can also consider her or the book store as being the Resource Server). Observe that, to grant permission to my friend (Client) to collect the books (resources), the lady (authorization server) selling the books called me (user) directly. This analogy describes the processes of the authorization code and the implicit grant types.Yes, because we have no token in the story, the analogy is partial and describes both cases.
Implementing the password grant type
In this section, we discuss the password grant type (figure 4). This grant type is also known as the resource owner credentials grant type. Applications using this flow assume that the Client collects the user’s credentials and uses them to authenticate and obtain an access token from the authorization server.
Figure 4 The password grant type assumes that the user shares their credentials with the Client. The Client uses them to obtain the token from the Authorization Server. It then accesses the resources from the resource server on behalf of the user.
You’ll use this flow only if the Client and authorization server are built and maintained by the same organization. Why? Let’s assume you build a microservices system, and you decide to separate the authentication responsibility as a different microservice. You do this separation to enhance scalability and to keep responsibilities separated for each service. (This separation is used widely in many systems. Let’s assume further that your system’s users use either a client web application developed with a frontend framework like Angular, ReactJS, or Vue.js. Or, they use a mobile app. In this case, the users might consider it strange to be redirected from your system to the same system for authentication and then back. This is what happens with a flow like the authorization code grant type. You expect to have the application present a login form to the user, and let the Client take care of sending the credentials to the server to authenticate. The user doesn’t need to know how you designed the authentication responsibility in your application.
Let’s see what happens when using the password grant type. The two tasks are:
- Requesting the access token
- Using the access token to call resources
Step 1: requesting the access token when using the password grant type
The flow is much simpler with the password grant type. The Client collects the user’s credentials and calls the authorization server to obtain the access token. When requesting the access token, the Client also sends in the request the following details:
- grant_type with the value “password”
- client_id and client_secret – the credentials used by the Client to authenticate itself
- scope – which you can understand as the granted authorities
- username and password – which are the credentials of the user. They’re sent in plain text as values of the request headers.
The Client receives back the access token in the response.
Step 2: using the access token to call resources when using the password grant type
Exactly as for the authorization code grant type, once the Client has an access token, it uses the token to call the endpoints on the resource server. The Client adds the access token to the requests in the
Authorization request header.
The password grant type in the analogy
To refer back to the analogy I made at the beginning of the article, imagine the lady selling the books won’t call me to confirm I want my friend to collect the books. Instead I give my ID to my friend to prove that I delegated my friend to get the books. See the difference? In this flow, I need to share my ID (credentials) with the Client. For this reason, we say that this grant type applies only if the resource owner “trusts” the Client.
Implementing the client credentials grant type
In this section, we discuss the client credentials grant type (figure 5). This is the simplest of the grant types described by OAuth 2. You use it when no user is involved – like when implementing authentication between two applications. I like to think about the client credentials grant type being a combination between the password grant type and an API key authentication flow.
We assume you have a system that implements authentication with OAuth 2. Now you need to allow an external server to authenticate and call a specific resource that your server exposes. You can create a custom filter to augment your implementation with a case of authentication using an API key. You could apply this approach using an OAuth 2 approach, and if your implementation uses OAuth 2, it’s undoubtedly cleaner to use the OAuth 2 framework in all the cases rather than augmenting it with a custom filter in your implementation, which lies outside of the OAuth 2 framework.
Figure 5. The client credentials grant type. We use this flow if a client needs to access a resource, but not on behalf of a resource owner. This resource can be an endpoint which isn’t owned by a user.
The steps for the client credentials grant type are similar to the password grant type, but the request for the access token doesn’t need any user credentials:
- Requesting the access token
- Using the access token to call resources
Step 1: using the access token with the client credential grant type
To obtain an access token, the Client requests the authorization server with the following details:
- grant_type with the value “client_credentials”
- client_id and client_secret representing the client credentials
- scope which represents the granted authorities
In response, the Client receives an access token. The Client can now use the access token to call endpoints of the resource server.
Step 2: using the access token to call resources with the client credential grant type
Exactly as for the authorization code grant type and password grant type, once the Client has an access token, it uses that token to call the endpoints on the resource server. The Client adds the access token value to the requests in the
Authorization request header.
Using refresh tokens to obtain new access tokens
In this section, we discuss refresh tokens. Up to now, you learned that the result of an OAuth 2 flow, which we also name grant, is an access token, but we didn’t discuss too much about this token. In the end, OAuth 2 doesn’t assume a specific implementation for tokens, but what you’ll learn now is that a token, no matter how implemented, might expire. It’s not mandatory—you can create tokens with an infinite lifespan—but in general, you’d prefer making them short-lived. The refresh tokens, of which we discuss in this section, represent an alternative to using the credentials for obtaining a new access token again. I’ll show you now how the refresh tokens work in OAuth 2
Let’s assume in your app, you implemented tokens that never expire. That means that the Client can use the same token, again and again, to call resources on the resource server. What if the token is stolen? In the end, don’t forget that the token is attached as a simple HTTP header on each and every request. If the token doesn’t expire, someone who gets their hands on the token can use it to access resources. A token that doesn’t expire is too powerful. It becomes almost as powerful as the user credentials. We prefer to avoid this and make it short-lived. This way, at some point, an expired token can’t be used anymore. The Client has to obtain another access token.
To obtain a new access token, the Client can rerun the flow depending on the grant type used. For example, if the grant type is authentication code, the Client redirects the user to the authorization server login endpoint, and the user must again fill in their username and password. Not user-friendly, is it? Imagine that the token has a twenty-minute lifespan and you work a couple of hours with the online app, and the app redirects you back to log in six times. Oh no! That app logged me out again!
To avoid the need to re-authenticate, the authorization server can issue a refresh token. This refresh token has a different value and purpose than the access token. The app uses the refresh token to obtain a new access token instead of having to re-authenticate.
Refresh tokens also have advantages over reauthentication in the password grant type. Even if with the password grant type, if we don’t use refresh tokens, we either need to ask the user again to authenticate or store their credentials. Storing the users’ credentials when using the password grant is one of the biggest mistakes you could do! And I’ve seen this approach used in real applications! Don’t do it! If you store the username and password – and assuming you save them as plaintext or something reversible because you have to be able to reuse them – you expose those credentials. Refresh tokens help you solve this problem easier and safer. Instead of unsafely storing the credentials and without needing to redirect the user every time, you can store a refresh token and use it to obtain a new access token when needed. Storing the refresh token is safer because you can revoke it if you find that it was exposed. Moreover, don’t forget that people tend to have the same credentials for multiple apps. Losing the credentials is way worse than losing a token tied to a specific application.
Figure 6. The refresh token grant: the Client has an access token, which expired. To avoid forcing the user to log in again, the Client uses the refresh token to issue a new access token.
Finally, let’s find out how to use a refresh token. Where do you get a refresh token from? The authorization server returns the refresh token together with the access token when using a flow like authorization code grant or password grant. With the client credentials grant, there’s no refresh token because this flow doesn’t need user credentials. Once the Client has a refresh token, when the access token expires, the Client should issue a request with the following details:
- grant_type with value “refresh_token”
- refresh_token with the value of the refresh token
- client_id and client_secret with the client credentials
- scope – can define the same granted authorities or less. If more granted authorities need to be authorized, a re-authentication is needed.
In response to this request, the authorization server issues a new access token and a new refresh token.
That’s all for this article.
If you want to learn more about the book you can check it out on our browser-based liveBook platform here.