English
  • auth

What is PKCE: from basic concepts to deep understanding

This article explains how PKCE (Proof Key for Code Exchange) secures OAuth 2.0 authorization code flow by preventing malicious applications from intercepting authorization codes, taking you from basic concepts to a comprehensive understanding.

Yijun
Yijun
Developer

Proof Key for Code Exchange (PKCE) is an extension to the Authorization Code flow, it was originally designed to secure the authorization code flow in mobile apps, and now it is recommended to be used by single-page apps as well. From OAuth 2.1, PKCE is enforced for all types of clients, including public clients and confidential (private) clients.

In this article, we'll help you understand why PKCE was created and how it protects your applications, giving you a deep understanding of PKCE.

Why PKCE is needed?

In the OAuth 2.0 authorization code flow, users request to sign in through an application. The authentication server directs users to an authentication page. After user authentication, the server returns an authorization code to the application, which then uses this code to request an access token from the authentication server.

This flow has a significant security risk: the authorization code can be intercepted by malicious programs. This is particularly concerning on mobile devices, where other applications may register the same redirect URI and intercept the authorization code.

The interception process is shown in the diagram below:

Step (1): The app performs a auth request through a secure API that cannot be intercepted. In this step, the requester also provides a redirect URI.

Step (2): The request then gets forwarded to the OAuth 2.0 authorization server. Because OAuth requires the use of TLS, this communication is protected by TLS and cannot be intercepted.

Step (3): The authorization server returns the authorization code.

Step (4.a): the authorization code is returned to the requester via the redirect URI that was provided in step (1). In this step, if the malicious app has registered itself as a handler for the redirect URI, and then the malicious app can intercept the authorization code. With the authorization code, the attacker can request and obtain an access token in steps (5.a) and (6.a), respectively.

As shown above, in the OAuth 2.0 authorization code flow, if the authorization code is intercepted, attackers can use it to obtain access tokens. Therefore, we need a mechanism to prevent authorization code interception, which led to the creation of PKCE.

How PKCE works?

As mentioned above, if we want to prevent being attacked, we need to ensure that only the app that initiated the request can request and obtain the access token. This is where PKCE comes into play.

PKCE solves this issue by introducing a "proof key" concept.

When requesting an authorization code, the application first generates a random code verifier and stores it locally. It then converts this code verifier into a code challenge using specific algorithms. The application sends both the code challenge and the code challenge method to the authentication server during the authorization code request.

The code verifier is a randomly generated string, and the code challenge is derived from the code verifier through conversion. Two conversion methods are supported:

  • plain: Uses the code verifier directly as the code challenge
  • S256: Applies SHA-256 hashing to the code verifier, followed by Base64URL encoding. Since the hash output cannot be reversed to obtain the code verifier, and because the plain method might be vulnerable to man-in-the-middle attacks during transmission, using S256 is strongly recommended for security reasons.

After user authentication, the authentication server returns the authorization code to the application. When requesting an access token, the application sends both the authorization code and code verifier to the authentication server. The server transforms the "code verifier" using the previously received "code challenge method" and compares the result with the previously received "code challenge" to verify the client's possession of the "code verifier".

Step (1-3): The app creates and records a secret named the "code verifier" and derives a transformed version "code challenge", which is sent in the OAuth 2.0 Authorization Request along with the transformation method "code challenge method".

Step (3-6): The Auth Server responds as usual but records "code challenge" and the "code challenge method".

Step (7.a): The app then sends the authorization code in to the token endpoint as usual but includes the "code verifier" secret generated at step (1).

Step (8.a-9.a): The authorization server transforms "code verifier" to "code challenge" and compares it to "code challenge" from step (1-3). Access is denied if they are not equal.

In this case, even though the malicious app have intercepted the authorization code at step (6.b), it is unable to redeem it for an access token, as it is not in possession of the "code_verifier" secret, and because the "code verifier" is sent over TLS, it cannot be intercepted.

Summary

This article explains how PKCE works and why it's necessary for protecting the authorization code flow. By adding a proof key mechanism, PKCE prevents malicious applications from intercepting and misusing authorization codes. Hope this explanation helps you understand PKCE in depth.