Shallow Dive: OAuth 2.0
Context
Now that I went over the basics of user authentication and session management (here). I was curious about how “login with X” worked. I eventually learned that it’s powered by OAuth 2.0. This post will do a shallow dive on OAuth 2.0 on what it is and how it works.
What is OAuth 2.0?
Let’s search the internet for it! Based on the internet, “OAuth 2.0 is the industry-standard protocol for authorization.” Authorization? Let’s search what that is too! From this article, it says “authorization is the process of giving the user permission to access a specific resource or function.” What?
Once you understand OAuth 2.0, these definitions make sense, but they were not helpful for me when I started the journey to understand OAuth 2.0. So, let’s take a step back and understand what is the problem that OAuth 2.0 is trying to solve.
Let’s say we are building a service, let’s call it an “AwesomeService”, that prints a user’s Instagram photos. When a user comes into our service, we want to show the user’s Instagram photos so that he/she can select the photos that he/she wants to print.
In order to do that, our service needs to have access to the user’s Instagram photos. One way to achieve this is to ask for the user’s Instagram account and password. Then, the service can log in to Instagram as the user and access the user’s photos. But there is a problem. Now the service can do much more than that. The service can delete the user’s photos or post a new photo. Basically the service can do whatever the user can do since the service now knows the user’s password. This is bad.
We want a way for “AwesomeService” to get permission to access the user’s Instagram photos without asking the user’s Instagram account password to “AwesomeService”. This is where OAuth 2.0 comes into the picture.
OAuth 2.0 is a protocol that enables a user to grant permission (or authorize) to a service to access the user’s resources stored in another service in a secure way. The resource could be the user’s profile information, photos, or really anything!
Let’s jump into how it works!
Terminology
OAuth2.0 is quite complicated and involves multiple components. Let’s establish terminologies before we get into the details.
Let’s continue with the “AwesomeService” example above. There is a website called “AwesomeService” that tries to access a user’s Instagram photos and John is trying to use the “AwesomeService”.
- Resource Owner: John - a user who owns the resource.
- Authorization Server: An Instagram server that authenticates the resource owner and provides an authorization token and an access token.
- Resource Server: An Instagram server that stores/provides John’s Instagram photos when given a proper access token.
- Client: AwesomeService - a service that’s trying to access resources from another service.
How does it work?
1000ft View
Let’s say John has logged into the “AwesomeService” website, and AwesomeService wants to access John’s Instagram photos to display it on its website for John to view and select. This kicks off OAuth 2.0 flow:
- AwesomeService sends a request to Instagram Authorization Server saying that we want to access the user’s Instagram Photos.
- Instagram Authorization Server authenticates the user.
- In other words, Instagram Authorization Server asks the user to provide the Instagram account and password – this is fine since it’s Instagram Authorization server asking for Instagram password.
- Once the authentication is successful, Instagram Authorization Server asks if John, the user, is okay with AwesomeService accessing his Instagram Photos.
- Once John says it’s okay, Instagram Authorization Server sends back an authorization token to AwesomeService.
- AwesomeService sends the authorization token back to Instagram Authorization Server to get an access token back.
- I will get back to why this step is necessary later.
- AwesomeService sends a request to Instagram Resource Server with the access token to fetch John’s Instagram Photos.
Closer Look
So, how does each step actually work?
- AwesomeService sends a request to Instagram Authorization Server saying that we want to access the user’s Instagram Photos. You can play around with this OAuth Debugger (https://oauthdebugger.com/) for this step.
- This step needs the following information
- Authorization server URI: Instagram’s (or Facebook) OAuth authorization server URI https://www.facebook.com/v12.0/dialog/oauth
- Redirect URI: Once the authorization is finished, the authorization service will redirect the user to this URI with the authorization token encoded as a query param.
- Client ID: This tells the authorization server which service is making the request. AwesomeService needs to register itself to Instagram OAuth beforehand to get this client ID.
- Scope: Scope tells the authorization server which permission this request is asking for. In our example, scope will be “read access to John’s Instagram photos”.
- Response type: This is either “code” or “token” or both. Let’s revisit this later.
- Response mode: I actually don’t know much about this field. Let’s keep it as a “query”. :)
- This information is passed as a GET request to the authorization server. That looks like the following:
https://<authorization-server-uri>?client_id=<client-id>&redirect_uri=<redirect-uri>&scope=<scope>&response_type=<code|token>&response_mode=query
- This step needs the following information
- Once John goes to the url generated from step 1, it will show the authentication UI for the authorization service.
- Once John successfully authenticates himself, the UI will ask for John’s permission to grant access based on the “scope” field from step 1 to AwesomeService.
- Then, the authorization service will redirect John to the Redirect URI from step 1 with the authorization token.
- When John sends a GET request to the redirect URI with the authorization token, AwesomeService backend can read the authorization token value as a param. Then, AwesomeService backend sends a request to Instagram Authorization service again with an authorization token and a secret key (a secret key generated when AwesomeService registers itself to Instagram OAuth) to get an access token.
- AwesomeService backend now has the access token, and can make a request to the resource server!
This is a high level flow of how OAuth 2.0 works!
What’s more?
Implicit Flow
The flow that I went through is usually called an “Authorization flow”. There are a few more flows in OAuth. The one that I will briefly touch on is an “Implicit flow”.
If you look at the “Authorization flow”, AwesomeService needs to get authorization token from Instagram Authorization server and then send another request to Instagram Authorization server again with the authorization token to get an access token.
This is to ensure the security of the access token. It assumes that the front end code that runs on the browser is not a super safe place. Authorization token which gets passed as a param to the redirect URI is exposed to the front end code. Then, the authorization token gets passed to the Awesome service backend, which then sends the direct request to the Instagram Authorization server with a secret key to get an access token. AwesomeService backend calls the Instagram Resource Server with the access token.
In this flow, the authorization token can be stolen but it’s fine since there is a secret key that only the AwesomeService backend and the Instagram Authorization server knows. The access token is also safe since it never left the AwesomeService backend.
This works when you have a backend. But nowadays, there are a lot of single page apps that don’t really have strong backend support. In that case, “Implicit flow” is used. In the “Implicit flow”, the step that returns authorization token in the “Authorization flow” will just return an access token directly. “Implicit flow” is considered less secure compared to the “Authorization flow”.
OpenID Connect
OAuth defines the overall flow but the granularity of “scope” is defined by each company implementing OAuth 2.0. The lack of standardization on “scope” led to a pain point in implementing “Log in with X”. For example, the scope for getting the user’s email in Instagram was different from Google.
OpenID Connect is an extension on top of OAuth 2.0 that said let’s make some common “scope” like “openid”, “profile”, and “email” for fetching basic user information to standardize user profile fetching use cases for OAuth.
There is an OpenID Connect debugger: https://oidcdebugger.com/
References
Thanks for reading through the post. This is just a worse version of the following video lectures:
- https://www.youtube.com/watch?v=t4-416mg6iU
- https://www.youtube.com/watch?v=996OiexHze0 Check out the videos to get more in-depth understanding with some cool visual aids.
If you are developing an OAuth use cases, these debugging tools are super helpful:
- https://oauthdebugger.com/
- https://oidcdebugger.com/