Diving deeper into OAuth 2.0

Christoffer Pedersen
7 min readAug 13, 2023

--

Welcome back to our exploration of the OAuth 2.0 and OIDC frameworks. Last time we explored the basics of OAuth 2.0 and OIDC using your valuable collection of LEGO sets that you store in a bank vault as an analogy. In today’s post, we’ll dive deeper into some of the workings of OAuth 2.0 and how you can best start integrating with it. If you haven’t read the last post, I’ll recommend you do so, before starting on this one.

There’s three topics we’ll go into today — scopes, tokens, and grant types. Knowing about these concepts will give you a better understanding of the framework and how to integrate with an authorization server.

OAuth Scopes

Seeing the headline above, the first question that pops into your head might be “what are scopes?”

Scopes are a concept in OAuth 2.0 that cover the mechanism which limits an application to what resources it can access. It’s important to remember that scopes should not be used to tell what the user of the application can or cannot access. This is a whole other topic I won’t go into here. Instead, scopes only specifies which consents you have given an application access to. If you have read my other post, you may remember my analogy about the bank vault and the courier accessing your vault with a keycard (if not I suggest you go read it!). With the keycard you are allowed to access the vault, it could also be locked down to only accessing some of the collectibles in the vault. Those permissions are comparable to scopes in the OAuth 2.0 world. When you allow the courier to get your keycard, you will also be presented with the permissions that the keycard contains, this is comparable to the consents you are shown when an application wants to get an access token.

Consent screen when linking Epic Games account with LEGO Account
Consent screen when linking Epic Games account with LEGO Account

The next question that we may ask is ”how do we let an application know what access you have granted it?” The place where your resources are being held is called a resource server. The application wants to access your resources through an API, which is being protected by a scope. Before it can call the resource API it needs to call an authorization server. The application does this by calling the /authorize endpoint on the authorization server, containing some query parameters, one of which is the scope parameter, along with the values that the application wants access to. If you haven’t already given the application consent to access those scopes, you will be presented with a consent screen, a screen you might have seen before if using Google or linked your Epic Games account with you LEGO Account, and the app wants access to your email or profile picture. After successfully calling the /authorize endpoint, the application will need to call the /oauth/token endpoint. When calling this endpoint the application specifies the grant type which it’s using. Depending on the grant type, the appropriate tokens are returned to the application.

Okayyy that was one long wall of text! Take a deep breath and just let that information sink in, before we dive some more …

You ready?

Access, ID, and Refresh tokens

As we mentioned before, it’s possible to get three kinds of tokens from the /oauth/token endpoint; an access token, ID token, and a refresh token. The ID token is almost self-explanatory. It contains data about your identity, whether it be your display name, first and last name, or something else related to your identity. This could be data you have entered yourself, or data that have been created on your behalf, like an ID generated when your identity was created. All this data in the ID token is called claims. Which claims the ID token contains is decided by the scopes that are being sent to the /authorize call. For example, the profile scope will result in an ID token that contains basic profile information like name, family_name, and so on. The profile scope is part of the OIDC specification along with the email, address, and phone scopes. The same for claims, they also belong to the OIDC specification.

Then we have the access token. This would contain scopes that you have consented to and have been allowed by the authorization server. The client will need to send this token with all requests to the resource server. Just because it contains the needed scopes for that resource endpoint, it doesn’t mean you actually have permission to access it, and that’s a very important thing to remember. The scopes only tell which of your user data you have granted a client access to. Of course this also means that the access token is very valuable, since it grants access to your data, so you don’t want it to fall into the wrong hands. One of the ways to protect the access token is making sure that it only lives for a short time. Does that mean you have to authenticate again after it expires? That depends on how the client is configured. If the client is configured to use the offline_access scope, you won’t have to authenticate every time the access token expires. Instead, the client can request a refresh token, the third kind of token, when it calls the /oauth/token endpoint.

The refresh token is the last of the tokens. As mentioned before, the access token is extremely valuable because it can give access to both reading and manipulating all your data that the token has access to. Therefore it’s often implemented to have a short lifetime. To solve the issue with having to authenticate every time the access token expires, it’s possible to get a refresh token along with it. The refresh token can be configured to either be one-time use or with a sliding expiration. This means that the client can keep getting a new access token by using the refresh token. But you may wonder, isn’t it even more powerful than the access token, and shouldn’t that warrant even greater protection? And yes it has more power, but compared to the access token, the refresh token can be revoked. So if the authorization gets a hint that someone may have stolen the refresh token, it should be invalidated.

What type of token you need heavily depends on the client that you have and what kind of grant type it’s configured to use.

Grant Types

There are three important grant types that you should know about. You may have encountered specifications or other articles that discuss additional grant types, but I won’t mention them here for several reasons; one - they are insecure, two - they are seen as legacy, and three - THEY ARE INSECURE!

Disaster girl meme. Implementing grant types — Implements implicit and password grants

Authorization Code (With PKCE)

In this grant type the authorization server acts as a mediator between the client and the resource owner. What you don’t want is to present your credentials to the client, so instead the authorization server will authenticate you, and then return an authorization code to the client, which it then can use to get an access token. By doing this we can also authenticate the client by providing client_secret and client_id . It’s also possible to transmit the access token directly to the client.

Increase security with PKCE

PKCE, short for Proof Key for Code Exchange, is an extension for the authorization code grant type. It provides even more security, protecting you against Cross-Site Request Forgery and authorization code injection attacks. You should be mindful that PKCE can’t replace client authentication, meaning that you can’t turn a public client into a confidential client.

This is implemented by letting the client create a secret (code_verifier) and a transformed version (code_challenge). It will then send the code_challenge and the transformation method to the authorization server, when asking for the authorization code. The authorization server stores the code challenge and the method. When the client wants to trade the authorization code to an access token, it will send the code_verifier along, letting the authorization server validate if it came from the correct client.

Client Credentials

Client credentials is the second grant type. It’s used for obtaining an authorization token when a user is not involved. This is also called machine-to-machine authorization. Due to everything happening from backend, the client can be considered confidential because it can keep a secret without leaking it.

The process works by the client calling the authorize endpoint, providing its client_id and client_secret as credentials. The authorization server then returns the requested token.

Device Code

The last grant type we’ll be talking about is the device code grant type. You have certainly run into this before, when you have tried to login to an application on a device that doesn’t have a browser e.g. Apple TV. This works by the device calling the authorization server that generates a code and a URL, which is returned to the device. The device then shows the code and URL to the user and starts polling the token endpoint. The user then goes to the URL on a device with a browser and then authenticates. After authentication, the user can enter the code and the device will be able to get the token from the token endpoint.

Ending notes

There’s a lot more to talk about on OAuth 2.0 and OIDC — this is still just scratching the surface. One thing to note in regards to the grant types, they are called flows in OIDC — same thing, different name.

Another helpful tip, when implementing either clients or your own authorization server, is to read the OAuth 2.0 Security Current Best Practices. This will prepare you to avoid many common pitfalls.

I hope you liked this article and have learned something new!

--

--

Christoffer Pedersen

Software Engineer for the LEGO Group. Building the backends of an awesome and child friendly CIAM system.