OAuth2 - Authorisation Code Flow with Proof Key for Code Exchange

OAuth 2.0 is the industry-standard protocol for authorisation, enabling applications to securely access user resources on behalf of the user. The Authorisation Code Flow is a popular OAuth 2.0 grant type used by web and mobile applications to obtain access tokens from authorisation servers. While secure in many scenarios, it can be vulnerable to certain attacks when used with “public” clients (applications that cannot securely store secrets). This is where Proof Key for Code Exchange (PKCE) comes into play.

For more information on OAuth 2.0 please read my OAuth 2.0 Introduction Article

PKCE Example

The Problem that Proof Key for Code Exchange solves

In the traditional Authorisation Code Flow, the client application redirects the user to the authorisation server for authentication and consent. Upon successful authorisation, the server sends an authorisation code back to the client application, which then exchanges this code for an access token. However, public clients cannot securely store client secrets, making them susceptible to authorisation code interception attacks.

PKCE addresses this vulnerability by introducing a dynamic “code verifier” and a derived “code challenge”. This mechanism ensures that only the client who initiated the flow can complete it, mitigating the risk of unauthorised token issuance

Example of the problem that Proof Key for Code Exchange solves

Scenario

  1. Malicious Actor: A malicious actor (Wesley) is operating a rogue app or website on the same device as a user (Ivan).
  2. Authorisation Request: Ivan wants to use a legitimate app (Bruce’s App), which is a public client (e.g., a single-page application). Bruce’s App initiates the OAuth 2.0 flow, redirecting Ivan to the authorisation server to log in and grant permission.
  3. Authorisation Code Interception: The authorisation server sends an authorisation code back to Bruce’s App through a redirect URI. However, Wesley’s malicious app intercepts this redirect URI, capturing the authorisation code.
  4. Unauthorised Token Exchange: Wesley, now possessing the authorisation code, can directly exchange it for an access token from the authorisation server. Since public clients don’t have a client secret to verify their identity, the server has no way to differentiate between Bruce’s App and Wesley’s malicious app.
  5. Access to Resources: Wesley can now use the acquired access token to access Ivan’s protected resources on behalf of Bruce’s App, even though Ivan never authorised Wesley’s app.

Vulnerability of Public Clients without Proof Key for Code Exchange

The core vulnerability stems from the fact that public clients cannot securely store client secrets. In the traditional Authorisation Code Flow, the client secret is used to authenticate the client during the token exchange step. However, since public clients are inherently open, the client secret would be easily exposed. Without this secret, the authorisation server has no reliable way to verify the client’s identity, opening the door for malicious actors to impersonate legitimate apps and steal authorisation codes.

Proof Key for Code Exchange Solution

PKCE mitigates this vulnerability by introducing a dynamic “code verifier” and a derived “code challenge.” The code challenge is sent in the initial authorisation request, and the code verifier (which is never transmitted) is required for the token exchange. This ensures that only the client who initiated the flow (and thus possesses the correct code verifier) can complete it.

Steps involved in Proof Key for Code Exchange

  1. Code Verifier Generation: The client application generates a high-entropy, cryptographically random code verifier.
  2. Code Challenge Calculation: Using a one-way hashing function (typically SHA256), the client calculates the code challenge from the code verifier and stores it during authorisation.
  3. Authorisation Request: The client sends an authorisation request to the server, including the code challenge and a method identifier indicating the hashing algorithm used.
  4. User Authorisation: The user authenticates and provides consent (if required) on the authorisation server.
  5. Authorisation Code Issuance: The server issues an authorisation code to the client.
  6. Token Exchange: The client sends the authorisation code and the original code verifier (not the code challenge) to the server’s token endpoint.
  7. Code Verification: The server verifies that the code verifier matches the code challenge it received earlier, confirming the client’s authenticity.
  8. Token Issuance: Upon successful verification, the server issues an access token to the client.

PKCE flow

Advantages of Proof Key for Code Exchange

  • Enhanced Security for Public Clients: Mitigates authorisation code interception attacks by proving that the client exchanging the code is the same one that initiated the flow.
  • Flexibility: Can be used with various client types, including single-page applications (SPAs), mobile apps, and even traditional web apps.
  • Standardised: Defined in RFC 7636, ensuring interoperability between different implementations.

Implementation Considerations

  • Code Verifier Entropy: Ensure the code verifier is sufficiently long and random to prevent brute-force attacks.
  • Hashing Algorithm: Use a strong, one-way hashing algorithm like SHA256.
  • State Parameter: Include a state parameter in the authorisation request to prevent cross-site request forgery (CSRF) attacks.
  • Client-Side Storage: Securely store the code verifier on the client-side until it’s needed for the token exchange.

Limitations of Proof Key for Code Exchange

Scenario

  1. Authorised Client Compromise: A malicious actor (Wesley) gains access to a user’s (Ivan’s) device and compromises a legitimate client application (Bruce’s App). This could happen through malware, a phishing attack, or other means.
  2. Session Hijacking: Wesley, now controlling Bruce’s App, initiates the OAuth 2.0 authorisation flow with PKCE. Since Wesley has control of the app, he can generate the code verifier and code challenge, and send them to the authorisation server.
  3. Code and Token Interception: Wesley, still in control of the compromised app, can intercept the authorisation code sent back by the server, as well as the access token obtained through the token exchange.
  4. Unauthorised Access: Even though PKCE is being used, Wesley has successfully bypassed its protection because he controls the client application itself. he can now use the stolen access token to access Ivan’s resources on behalf of Bruce’s App, even though Ivan never authorised Wesley.

The Limitation

This scenario highlights that PKCE, while effective against authorisation code interception attacks, does not protect against client compromise. If a malicious actor can gain control of the client application itself, PKCE’s mechanism for ensuring the client’s authenticity becomes irrelevant.

Mitigation Strategies

While PKCE alone cannot prevent client compromise, you can combine it with other security measures to enhance overall protection:

  • Strong Client Authentication: If possible, use client authentication methods like client secrets (for confidential clients) or stronger authentication techniques like MTLS (Mutual Transport Layer Security) to verify the client’s identity beyond PKCE.
  • Device Security: Encourage users to practice good device security hygiene, such as using strong passwords, avoiding suspicious downloads, and keeping software updated.
  • Secure Communication: Always use HTTPS for all communication throughout the OAuth 2.0 flow to protect data in transit.
  • Monitoring and Anomaly Detection: Implement monitoring mechanisms to detect unusual activity patterns that might indicate a client compromise.

Conclusion

The OAuth 2.0 Authorisation Code Flow with PKCE significantly bolsters the security posture of public clients, such as Single-Page Applications (SPAs) and mobile apps, by effectively mitigating the risk of authorisation code interception attacks. By integrating PKCE into your OAuth implementation, you introduce an additional layer of protection that ensures only the legitimate client can exchange the authorization code for an access token, safeguarding user data and fortifying your application’s overall security.

However, it’s crucial to remember that PKCE is not a silver bullet for all security concerns. While it addresses the specific vulnerability of code interception, it doesn’t protect against client compromise. Therefore, it’s imperative to combine PKCE with other security best practices, such as strong client authentication, robust device security, secure communication protocols, and vigilant monitoring, to create a comprehensive defense against a wide range of potential threats.

By adopting a multi-layered security approach that includes PKCE and other safeguards, you can confidently build and deploy applications that prioritise the privacy and security of your users, fostering trust and ensuring the integrity of your OAuth-based authorisation process.