OAuth 2.0 – Foundation for Secure Systems
OAuth 2.0 is an authorization framework that allows a client to get access to specific HTTP service wherein Open ID Connect (OIDC) is a simple identity layer on top of OAuth 2.0. What I just said might read plain simple but it’s quite confusing, aiy?. See, originally OAuth was built with the intent to the authorization piece alone – “who can access what”. But once OAuth started getting used widely, the question came up as to how to leverage OAuth to also support authentication (“who is it?”). That’s when OIDC was built on top of OAuth. To quote this with an example, let us assume Azure Active Directory (AAD) is our Identity & Access Management (IAM) solution. How do you build an application that leverages AAD as the Identity Provider? Well you guessed it right! You need to use OAuth 2.0 & OIDC. Rather, you need OIDC (as OIDC implicitly means OAuth is already in play, right?)
Before you go in and build a system, its better to understand OIDC better so you build the solution with a ‘secure by design’ mindset. You may ask – “why?” (That’s a good question to always ask, in case you didn’t ask already! 😊 ). Most of the data breaches and data loss happens not always due to bad software code quality but due to lack of design decisions. Enough of that, let us get into what OIDC & OAuth exactly does.
OAuth defines 4 roles:
a. Client (client) – An application making a request for a resource, typically a thin or thick client.
b. Resource Owner (RO) – An entity capable of providing access to a protected resource. It is typically the end user.
c. Resource Server (RS) – The server where the protected resource is kept. Typically, a Web API.
d. Authorization Server (AS) – The server issuing access token to the CL after successfully authenticating the RO. Typically, an Identity Server.
The diagram below provides a better representation of the same:
Just to expand on this diagram via an example, let us say Joe is logging into an web application to access the company financial data that is exposed to the web application via a secure API. Here Joe is the RO and web app is the client and the Web API is the RS and the client interacts with Azure AD (Okta, ADFS etc) to obtain the grant to access the API – thereby Azure AD is the AS in this instance.
OAuth defines several mechanisms for a client to talk to AS and obtain the grant to access the RS. They are defined below:
I. Authorization Code Grant:
The client will redirect the user to an AS. The AS authenticates the user using username/password (or MFA, biometrics etc.) and then once authentication is successful, provides a authorization code back to the client via a redirect URI that the client shared with the initial request. The client then sends back a POST request to the AS using the authorization code and the same redirect URI, this time the AS can ensure that the client is a valid client and then provides an authorization token to client to access the RS.
This is a two-step process wherein first step is a “re-direct” action using the user-agent to authenticate the end user (RO). In this step, the re-direct request shall include the following information:
- response_type – value shall be “code”. This is basically telling the AS that the client is expecting a authorization code a response.
- client_id – This is letting the AS know the Application ID of the client. Now you should have already figured out that the application has to be registered in AS and proper client_id provided by AS should be used by client to identify itself in all communications with AS.
- redirect_uri – This is the URI where AS can respond back to the client with the code.
- scope – A space delimited list of scopes. These are string values and typically defined by the AS.
- state – This is a random unique value, the purpose of this is to maintain state between request and response. In other words, AS will send the same state value in its response. This will allow the client to make sure this response is for its specific request. Think of it as a Request ID.
Now the AS performs authentication of the user and once that is successful, it issues the response back to the client with the following information:
- code – the authorization code for the client. This is a short lived code with life of 10 minutes.
- state – the value that was sent in the original request (Refer Request ID mentioned above)
This is sent back to the client using the re-direct URI passed onto AS in the original request. Now step 1 is complete and the user (RO) authentication has been completed.
Once that is done, the second step is a “POST” action where the client validates itself and confirms the AS that it has received the code. Now AS can be assured that the client is not a rogue app as well as the end user is a valid user. Now its ready to grant access via the token.
The second POST request to AS from client includes the following information:
- grant_type – value should be ‘authorization_code’
- code – The value received in the step 1 response from AS
- redirect_uri – same URI send in the first part of the request
- client_id – same as explained in Step 1
- client_secret – This is the secret that is provided to the client when registering the client in AS. This secret can be used only by applications that store a secret – for example Single Page Apps cannot store the secret as there is no server component to store the secret safely.
Once AS cross check the Client ID, Client Secret combination as well as the ‘code’ that was send by it in step 1, it now ensures that this is a valid registered client with an authenticated user and hence issues the following response as a JSON object:
- token_type – This would be normally ‘Bearer’
- access_token – The requested access token. The app can use this token to authenticate to the secured resource (RS), such as a web API.
- expires_in – How long is the token valid specified in seconds
- refresh_token – An OAuth 2.0 refresh token. The app can use this token acquire additional access tokens after the current access token expires. Refresh_tokens are long-lived, and can be used to retain access to resources for extended periods of time. Usually refresh tokens are issued only if the Scope provided in the request contains “Offline_Access” as the value.
- id_token – A JSON Web Token (JWT). The app can decode the segments of this token to request information about the user who signed in. The app can cache the values and display them, but it should not rely on them for any authorization or security boundaries. This is issued only if the scope “openid” was mentioned in request.
In fact, there is a Step 3 here! This is when our client reaches out to RS (Web API) with the access token. The client passes the access token in the “Authorization” header of the HTTP request along with the URL for the resource. Upon receiving the request, the RS validates the access token in the header and once that validation is complete, it provides access to the resource aka API.
II. Implicit Grant:
The implicit grant is very similar to the authorization code grant with two distinct differences. It is intended to be used for user-agent-based clients like single page web apps (SPA) that cannot keep a client secret because there is no server component and all application code and storage is easily accessible. Secondly instead of the AS returning an authorization code which is exchanged for an access token, the AS returns an access token. As you can see, the implicit grant type does not include client authentication, and relies on the presence of the resource owner and the registration of the redirection URI. Because the access token is encoded into the redirection URI, it may be exposed to the resource owner and other browser add ins etc.. Hence this is relatively less secure than Authorization Code Grant.
- The request shall be similar to the Step 1 in Authorization Code Grant except that the response_type value shall be ‘token’. (instead of ‘Code’)
- The response shall be similar to the Step 2 response in Authorization Code Grant except that refresh_tokens are never issued in Implicit grant response as the client has no ways of securing it.
Now you might be wondering, so then does a single page app have to repeatedly prompt user for credential as it does not have refresh tokens? Conveniently , a JavaScript application has another mechanism at its disposal for renewing access tokens without repeatedly prompting the user for credentials. The application can use a hidden iframe to perform new token requests against the authorization endpoint. As far as the application browser maintains the AS session cookie, it can make that new request agnostic to the user knowing anything about it.
III. Client Credentials Grant:
Consider scenarios where a background job (or a service) needs to access a secure Web API. These are backend scenarios where the end user (RO) is not involved. In such scenarios, the process is similar to Step 2 of Authorization Code Grant but without a code parameter. Also it passes the grant_type as “client_credentials”. Along with it the client_id and client_secret are passed. Once the AS validates the same, it sends back an access token without a refresh token.
During the registration of the apps, the administrator can provide necessary consents for resource to resource access so that any consent challenges are addressed.
IV. Resource Owner Credentials Grant:
Think of scenarios where you have a trusted and secure first class organizational apps and you would want to avoid re-directs and provide a seamless experience to users. Is that possible? Yes, you can avoid the redirect to AS for authentication and ask the application to handle the user interface for authentication. The actual authentication still happens in AS (as your app will pass back the UID/PWD to AS) however the user experience stays within the trusted app. This scenario can be implemented for web or native client apps.
In such scenarios, the client captures the RO username and password, and then POSTS the details to AS along with the client_id and client_secret. The request looks like below:
- grant_type – value shall be ‘Password’
- client_id – client ID
- client_secret – unique secret code for the app
- scope – defined scope for which request is made.
- username – This is user’s entered username in the UI of the app
- password – This is the user’s entered password in the UI of the app
The response in this scenario is similar to Step 2 response of the Authorization Code Grant and contains the access and refresh tokens to be used by the client app.
There are more OAuth grant types, however these are the basic grant models. For now, it is important to understand these basic grant models and realize that the following factors determine which grant model to choose for which implementation scenarios:
a. Is your application a trusted organizational app or a non-trusted third-party app?
b. Is your app web based or thick client or mobile native solution?
c. Is your application a Single Page App?
d. Is your application consuming secure APIs?
e. Do you have secure APIs that needs to be consumed by other secure APIs or jobs?
While building a secure solution, it’s always important to focus on two critical components:
1. During design of any software system, always focus on the right type of security needed for the problem at hand – there is no one size fits all approach to designing secure systems – for example, don’t use Implicit Grant when Authorization Code Grant could be used.
2. Secondly, always follow the principle of least privilege when granting access to users as well as to resources – for example using the right “scope” when providing access to an API ensures that only relevant data is shared and authorized for access by the client.
Of course, there is more to building secure business systems and applications, but these two principles are a good place to start. And to do that it is important that you have a good understanding of the underlying mechanisms and always have a ‘secure by design’ mindset. Good luck with your solution designs!