OAuth and PKCE flow - Part 1

- Hariharan

Source - Wikipedia

OAuth 2.0 (referred to as OAuth from now on) is an open standard for secure access delegation and it provides mechanisms for clients to obtain access to resources on a server on behalf of the user. To achieve this OAuth uses access tokens. An access token, simply put, is a string representing the granted permissions. The permissions represented in the access token are called scopes in OAuth speak.  OAuth defines different flows to obtain an access token. PKCE is one such flow. To understand PKCE, one has to have a basic understanding of the implicit flow and the code grant flow to see why PKCE exists and when to use it.

Implicit Flow

Implicit flow is the oldest of the bunch, created around 10 years ago. The web landscape was significantly different back then. Browsers were not allowed to make cross-origin requests. So JavaScript applications could not make requests to a different domain. To overcome these limitations implicit flow was proposed which eliminated the need to make requests to external domains by returning the token in the redirect url as a query param.

Steps to obtain an access token using implicit flow

As you would have noticed by now, this isn’t very secure. OAuth 2.0 spec does not provide any mechanism for clients to verify that the token was issued to them. This exposes a risk of impersonation attacks. There are many ways the redirect could fail or be intercepted thus exposing the access token to malicious agents.

It is also difficult to implement this flow in native applications.

Authorization Code Grant Flow

Authorization code grant flow tried to solve some problems with the implicit flow.

Source - Digitalocean

Steps to obtain an access token in the code grant flow

The application makes a POST request to exchange the code for an access token. By the time this flow was proposed, browsers allowed cross-origin requests. The code can only be used once to obtain the access token. It is worth noting that a refresh token is also returned with the access token in this flow. The refresh token can be used to obtain a new access token. Refresh token allows a user session to continue without the need to sign-in every so often. 

It is important to note that in order to obtain the authorization code, the client has to pass a client ID and client secret to the OAuth server. These credentials can be obtained when setting up the application on the OAuth server. This proposes a new challenge when building web applications such SPA, storing client secrets in the frontend. Doing so would expose it and cause a major security flaw.

PKCE Flow

PKCE (Proof Key for Code Exchange) builds on top of the code grant flow and addresses issues such as authorization code interception and eliminates the need for client secrets. That being said, you can still use client secrets with PKCE flow to prevent just any client from potentially sending requests to your OAuth server.

Source - Auth0

When an application needs to perform an authentication against the OAuth server, instead of directly opening the browser and redirecting the user to the OAuth server, the client first creates a code verifier. This is a cryptographically random string that can be made of characters A-Z, a-z, 0-9 and punctuation characters - hyphen, period, underscore, and tilde. If the device is capable of performing a SHA256 hash, the code verifier is hashed. The code verifier could also be used as plain text if the device can’t perform a SHA256 hash.

The client then makes an authorization request with the following parameters -

It is worth noting that the only additional parameters here as compared to the Authorization Code Flow are code_challenge and code_challenge_method. The authorization server recognizes the code_challenge parameter in the request and associates it with the authorization code and stores it. It then returns an authorization code as normal.

The client then makes a code exchange request with the following parameters -

The authorization server first checks the code_challenge_method that it received in the previous request. If the method is plain, the server can just check the code_verifier against the code_challenge it received earlier. Else if the method is S256, the server must take the code_verifier and transform it into a SHA256 hash and base64 encode it before a code_challenge match is performed. The server only returns an access token if the code_verifier is correctly matched.

Why PKCE?

PKCE was initially designed for native apps (Android, IOS, electron etc). To utilize OAuth in these cases a browser based login is required and you would be leaving the security context of your application. You might not have a secure redirect to your application from the browser and the authorization code could be intercepted. PKCE solves this by establishing a shared secret between the application and the OAuth server. Even if the authorization code is intercepted, without the code verifier the requests to the OAuth server will be invalid.

PKCE flow is great for public clients (clients that can’t safely store client secrets). PKCE flow’s ultimate aim is to avoid leaked authorization codes from being useful.

In part 2 of this post we will look at an example client in Golang that uses the PKCE flow to obtain an access token. So stay tuned for that!

Further Reading