Alexander Garcia
Differences between OAuth 2.0's Proof Key for Code Exchange (PKCE) and Client Authentication Private Key JWT
Read time is about 9 minutes
Alexander Garcia is an effective JavaScript Engineer who crafts stunning web experiences.
Alexander Garcia is a meticulous Web Architect who creates scalable, maintainable web solutions.
Alexander Garcia is a passionate Software Consultant who develops extendable, fault-tolerant code.
Alexander Garcia is a detail-oriented Web Developer who builds user-friendly websites.
Alexander Garcia is a passionate Lead Software Engineer who builds user-friendly experiences.
Alexander Garcia is a trailblazing UI Engineer who develops pixel-perfect code and design.
When it comes to OAuth 2.0, there are two popular ways to secure client credentials - Proof Key for Code Exchange (PKCE) and Client Authentication Private Key JWT. Although both methods are used to authenticate, they differ in some significant ways. In this blog post, we'll discuss the differences between PKCE (pronounced pixie) and Client Authentication Private Key JWT.
/authorize, /token, /refresh)/token and /refresh)Proof Key for Code Exchange, or PKCE, is an extension of OAuth 2.0, which is designed to secure native, mobile, and web applications (basically anything that has a "keyboard & mouse"). PKCE adds additional security to the Authorization Code flow by binding the access token to a specific device and user. PKCE accomplishes this by using state, a code verifier and a code challenge to prevent Authorization Code interception (also known as Man-in-the-Middle MITM) attacks.
To use PKCE, the client application generates a state, code_verified, and a code_challenge.
code_verifier is a one-time random secret string (minimum of 64 characters long)state is a one-time random string, (minimum of 28 characters long)code_challenge, a hashed value (usually SHA256) of the code_verifier.The client application generates both the code_verifier and state and saves it in storage (either Local Storage or Session Storage). It then sends the code_challenge to the authorization server when requesting an Authorization Code.
/* OAuth 2.0 Authorize (/authorize) with PKCE */ // Generate a state const state = generateRandomString(28); localStorage.setItem("state", state); // Generate a code verifier const codeVerifier = generateRandomString(64); localStorage.setItem("code_verifier", codeVerifier); // Generate a code challenge const codeChallenge = await sha256(codeVerifier); const authorizeUrl = ` <authorization-server-url>/authorize/ ?client_id=client_requesting_auth &response_type=code &state=982407f7db5a35f6bd8bdcd83264476ce1d2f7d170246dbbcb7086b4 &code_challenge=sCHXzXpnKE5V9z_upOqcxhBx1ihW_L4qqwGPxMcE9TY &code_challenge_method=S256 `;
The authorization server verifies the code_challenge and responds with the Authorization Code code and the initial state. The client application (aka "the browser") then checks to see if the state is the same as the original request and requests to exchange the authorization code for an Access Token, which is then used to access protected resources.
/* OAuth 2.0 Token Exchange (/token) with PKCE */ // User lands on a callback URL that has `state` and `code` window.location = `<client-url>/callback/ ?code=319311a3bb3f13f98f8f3ace3fa23j &state=982407f7db5a35f6bd8bdcd83264476ce1d2f7d170246dbbcb7086b4`; const savedLocalStorageState = localStorage.getItem("state"); const queryParameterState = new URL(window.location).searchParams.get("state"); /* Compare state saved in LocalStorage (during initial `/authorize`) with the `/callback/?state=<should_be_same_state> */ if (savedLocalStorageState === queryParameterState) { // if state matches, request code for an access token const url = ` <authorization-server-url>/token/ ?client_id=client_requesting_auth &grant_type=authorization_code &code=319311a3bb3f13f98f8f3ace3fa23j &code_verifier=175618f6eb0acc38891b3037b37df7de20f901ade08ad9124804... `; const response = await fetch(url); // request for token const data = await response.json(); // keep access tokens SECURE! } else { throw new Error("state did not match request"); }
Client Authentication Private Key JWT, also known as OAuth JWT, allows clients to authenticate with the authorization server using a private key. This method is typically used by server-side applications that require high levels of security.
To use OAuth JWT, the client application generates a private/public key pair. The client application sends a request to the authorization server to register the public key. When the client application requests an access token, it signs the request with its private key. The authorization server verifies the signature using the registered public key and responds with an access token.
const jwt = require('jsonwebtoken'); const axios = require('axios'); // Generate a private/public key pair const { privateKey, publicKey } = jwt.generateKeyPairSync(); // Request an access token signed with the private key const jwtPayload = { iss: 'client-id', sub: 'client-id', aud: '<https://auth.example.com/token>', iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 60, }; // Sign the JWT token with the generated private key const token = jwt.sign(jwtPayload, privateKey, { algorithm: 'RS256' }); // Create request for `/token` endpoint with the following options const response = axios.post('<https://auth.example.com/token>', { // REQUIRED grant_type: 'client_credentials', // Optional, client_id already in signed JWT client_id: 'client-id', client_assertion_type: 'urn:ietf:params:oauth:grant-type:saml2-bearer' client_assertion: token, //PHnbzW0...ZT }); // Use access token const accessToken = await response.data;
The main differences between PKCE and Client Authentication Private Key JWT are:
code_verifier and code_challenge to secure the Authorization Code flow, while Client Authentication Private Key JWT uses a private/public key pair.| PKCE | Private Key JWT | |
| Encryption | Symmetric usually (HMAC) SHA-256 | Asymmetric (RSA Signature) usually with SHA-256 |
Client Type | Web (single-page applications), native, and mobile | Machine-to-machine or server-side applications (CLI, API-to-API, etc) |
| Secret | One-time random string | Encrypted JWT with Private Key (Public Key is exposed to Authorization Server) |
In conclusion, both PKCE and Client Authentication Private Key JWT are effective methods for securing client credentials in OAuth 2.1. However, the method you choose will depend on the type of application you are building and the level of security you require.
If you want to learn more about these topics, check out oauth.net, oauth.com, or IETF OAuth RFCs
Hopefully some of you found that useful. Cheers! 🎉