In our last blog post on web authentication, you were introduced to the OAuth 2.0 protocol. In this and the following posts, we’ll be taking a deeper dive into the different flows, or implementations, of the OAuth 2.0 protocol.
The Authorization Grant
Before we start talking about specific flows, it’s important to understand what an authorization grant is. The Internet Engineering Task Force (IETF) defines it this way:
An authorization grant is a credential representing the resource owner’s authorization (to access its protected resources) used by the client to obtain an access token.1
To summarize, the client uses the authorization grant to get an access token from the authorization server (if you are a bit fuzzy on this, check out the diagram from our last blog post). There are six types of authorization grants defined by the RFC specification:
- Authorization code
- Implicit (legacy, replaced by PKCE flow)
- Client credentials
- Password grant (legacy)
- Device code
- Refresh token
All of these grant types, with the exception of the refresh token grant, also represent different OAuth 2.0 flows. The refresh token grant type may or may not be used in conjunction with a flow to extend the period of time in which the client is authorized to access protected resources, but we’ll talk about that in a later post. Our focus today will be on the authorization code flow, as this provides a good point of comparison for the other flows.
Authorization Code Flow
The authorization code flow is used to obtain both access and refresh tokens. It is what’s called a “redirection-based” flow, meaning it relies on the ability of the client to interact with the resource owner via a user-agent that can handle incoming redirect requests from the authorization server. To understand this a little better, let’s define the four roles we just outlined:
- Client – application requesting access to protected resources (e.g. a web server)
- User-Agent – software acting at the direction of the user (e.g. a browser)
- Resource Owner – the user that owns the resources (e.g. the end user)
- Authentication Server – trusted server storing client credentials and in charge of issuing authorization grants
So, how do these roles interact when authorizing a client? The RFC specification starts it’s definition of the flow with the client initiating the request, but typically this is kicked off by a user agent making a request to the client, like when a user visits a webpage that is or contains a protected resource on their browser. Thinking of our roles, this constitutes a user-agent making a request to the client (step 0 in the diagram).
Then, the client – wanting to access protected resources – redirects the user-agent to the authorization server’s authorize endpoint, passing along its client id (a unique string representing the registration information provided by the client) and the redirect URI (the registered URI in which to redirect the user-agent once successfully authenticated)(step 1). It may also optionally pass any local state that the client wished to be preserved throughout the authorization transaction (e.g. the originally requested resource). This is an example of what this request from the browser to the authorization server looks like (after the redirect from the client has been processed):
The authorization server then redirects the user-agent to a login page, hosted by the authorization server itself, to gather the resource owner’s credentials. Upon successful submission (step2), the authorization server sends a redirect to the user-agent, instructing it to go to the redirect URI (step3). In this redirect, it sends the authorization code in a query parameter, along with the local state that it was initially provided:
Now the client has the authorization code, which it will use to issue a request to the authorization server’s token endpoint in exchange for an access token that will allow it to access protected resources (step 4). This request must use the application/x-www-form-urlencoded format and send the authorization code, its client id (either in the basic authorization header or as a query param), and the redirect URI in the request to validate the authorization code given, matches the client it was issued to:
If this was successful, the authorization server responds with an access token, and may also include a refresh token if it was requested (step 5). Now the client can use the access token to access protected resources.
Benefits of the Authorization Code Flow
Using the authorization code flow has a few security benefits, namely keeping client and user credentials separate. By using the authorization server as an intermediary, user credentials are never shared with the client. This means that if the client has a security breach, user credentials are not exposed.
Another benefit of the authorization code flow is that it allows us to authenticate the client itself, so the authorization server knows where the request is coming from and who exactly authorized it. The access code is only shared with the client, so it reduces the risk of the access code being used by a malicious third party. Provisions in the protocol disallow the reuse of authorization codes and recommend that if an authorization code is reused, all tokens tied to that authorization code immediately be revoked. This prevents impersonation of the client.
Drawbacks to the Authorization Code Flow
Despite the security benefits gained by using the authorization code flow, usage is limited by a few constraints. For one, it requires a user-agent capable of redirects. This makes it impractical for most mobile applications. Another constraint is that it should use what the IETF calls a confidential client – a client capable of maintaining the confidentiality of its credentials.2 A common example of this is a server with protected access to it’s credentials, or a cloud function that has permissions to access securely stored credentials in the cloud. Examples of what the IETF deems a public client (a client incapable of maintaining the confidentiality of its credentials) would be a front-end web application that does not have a secure backend, or a native mobile or desktop application without similar backend support.
The authorization code flow provides some nice security benefits, and is a great way to secure a client-server application. However, if you are developing an application without a secure backend, you will likely need to consider an alternative authorization grant, such as the PKCE flow, the topic of our next blog post in this series.
Check back for future posts on OAuth 2.0 in web applications. Have questions? Contact us or join in the conversation below!
- Website Authentication – Part 3: OAuth 2.0 Authorization Code Flow - April 16, 2020
- Website Authentication – Part 2: Intro to OAuth 2.0 - March 5, 2020
- If You Give a Dev a Cookie… - February 6, 2020