DEV Community

Cover image for PKCE Demystified: How OAuth 2.0 Became More Secure for Public Clients
Aman Kr Pandey
Aman Kr Pandey

Posted on • Edited on

PKCE Demystified: How OAuth 2.0 Became More Secure for Public Clients

OAuth2.0 has been a very popular protocol among developers for delegating authorization for modern apps. But when it comes to mobile apps or single page application (SPAs), the traditional Authorization code flow done with the help of client_id and client_secret has a serious security vulnerability: Code Interception.

Here enters: PKCE (Proof Key for Code Exchange) — a security enhancement that protects public clients from code interception attacks. This post breaks down what PKCE is, how it works, and how to implement it.

Need of PKCE Flow

In the traditional OAuth2.0 Authorization code flow, the client first sends authorize request to authorization server with client_id, the server then responds back with a authorization_code and then the client sends a request with the authorization_code and client_secret. Now, the problem here is that public client like mobile apps and SPAs can not store secrets securely and this makes them vulnerable to interception attacks. So, your authorization_code and client_secret once they get intercepted by an attacker can use them to initiate token exchange and access resources. PKCE flow mitigates this by adding a dynamic challenge and verification in each authorization request. This ensures that only the app has the ability to get authorized not any attacker even if they have intercepted the authorization_code.

How PKCE Flow works

Let's walk through the PKCE flow and how it works:

1. Client generates Secret and Challenge

In this steps the client create a Secret and a Challenge. Secret is any random string and the Challenge is hashed version of the Secret using any of the cryptographic algorithms (SHA3-256 in our case).

code_verifier = randomString(43-128 chars) 
code_challenge = base64url(SHA3-256(code_verifier))
Enter fullscreen mode Exit fullscreen mode

2. Client send authorize request with code_challange.

The client initiates authorization.

GET /authorize?
  response_type=code&
  client_id=your-client-id&
  redirect_uri=https://yourapp.com/callback&
  code_challenge=f591f035a31f320015177cadb73e2056939b5ddbd30e902c5f274bc092b29545&
  code_challenge_method=SHA3-256
Enter fullscreen mode Exit fullscreen mode

So there are two methods to pass code_challange_method. First we can save it in the database of our authorization server as part of client's config, secondly we can pass it in the URL as a parameter, to add an extra layer of security in second method we can keep changing the method in each request.

3. User approval and server's redirect

The user logs in and approves apps access to its resources, and the authorization server redirects to client's callback url with authorization_code.

Once the user is authenticated the server stores the code_challenge and code_challenge_method against authorization_code that it issued.

4. Client initiates token exchange

Now once redirected, the client then uses authorization_code to get tokens in exchange. Here the client sends code_secret as parameter in request. This is the original Secret generated in the first step.

POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=XYZ&
code_secret=some_code
Enter fullscreen mode Exit fullscreen mode

The server compares the hash of code_secret with the earlier code_challenge. If they match, it issues tokens. Here while generating the hash from code_secret one may introduce hash rounds a client's config, which is stored over database and only known to authorization server and the client. Adding hash rounds makes apps more secure and even less vulnerable to attacks. Even if someone mimics the PKCE flow, creating their own secret and challenge, they might not be aware of exact number of hash rounds.

When to use PKCE?

Use PKCE whenever your app is a public client:

  • Native mobile apps (iOS, Android)
  • Single Page Applications (React, Angular, Vue)
  • Desktop apps (Electron, etc.)

Implementation Tips

  • Use SHA-256 as cryptographic method, or keep changing for each request
  • Never re-use code_secret, always generate new Secret for new request
  • Keep the PKCE logic in secure memory, avoid writing to local storage in browsers

Conclusion

So, at last we can conclude that OAuth2.0 can be a great choice to authorize users if you are building a public client, but with PKCE it adds more security with minimal complexity.

Thanks for you time!
Have a great day ahead!

Top comments (0)