PKCE - The new standard
PKCE (Proof Key for Code Exchange) Implementation
PKCE (pronounced "pixy") is an OAuth 2.0 security extension designed to protect authorization code flow against authorization code interception attacks. It was originally developed for mobile applications but is now recommended for all clients that cannot maintain client secrecy.
Benefits Over Traditional Authorization Code and Implicit Grant
Enhanced Security for Public Clients: Unlike traditional authorization code flow which relies on client secrets, PKCE uses dynamically created secrets, making it suitable for applications where client secrets cannot be securely stored (mobile apps, SPAs).
Protection Against Code Interception: PKCE prevents attackers from intercepting authorization codes and exchanging them for tokens, as the code verifier is required during token exchange.
Superior to Implicit Grant: PKCE eliminates the security risks of implicit grant flow where access tokens were returned directly in the URL fragment, vulnerable to browser history and referrer header leaks.
No Client Secret Requirement: Removes the need for hard-coded client secrets in applications where they cannot be protected, reducing attack surface.
Prevents Authorization Code Injection: The code verifier binding prevents attackers from injecting stolen authorization codes from other sessions.
PKCE Full Flow
Step 1: Code Verifier and Challenge Creation
Client generates a cryptographically random code_verifier (43-128 characters, using A-Z, a-z, 0-9, -, ., _, ~)
Client creates code_challenge by hashing code_verifier with SHA-256 and base64url encoding
Client stores code_verifier securely for later use
Step 2: Authorization Request
Client redirects user to authorization endpoint with:
response_type=code
client_id
redirect_uri
code_challenge
code_challenge_method=S256
state (CSRF protection)
Step 3: User Authentication and Consent
User authenticates and grants consent at authorization server
Authorization server stores code_challenge associated with the authorization code
Step 4: Authorization Response
Authorization server redirects to client's redirect_uri with:
authorization code
state (must match request state)
Step 5: Token Request
Client makes POST request to token endpoint with:
grant_type=authorization_code
code (authorization code received)
redirect_uri (must match initial request)
client_id
code_verifier (original random value)
Step 6: Token Response
Authorization server:
Hashes code_verifier using SHA-256 and compares with stored code_challenge
If match is successful, issues access token and optionally refresh token
Returns tokens to client
Security Considerations
Code Verifier Entropy: Must be cryptographically random with sufficient length (recommended 32+ bytes)
Code Verifier Storage: Must be protected in client storage and not exposed in logs or URLs
TLS Mandatory: All communications must use HTTPS to prevent interception
Challenge Method: S256 (SHA-256) is strongly preferred over plain method
State Parameter: Must still be used for CSRF protection alongside PKCE
Token Storage: Received tokens must be stored securely on client side
Short Authorization Code Lifetime: Authorization codes should expire quickly (recommended 10 minutes or less)
Best Supported Application Types
Native Mobile Applications: Primary use case where client secrets cannot be protected
Single Page Applications (SPAs): JavaScript applications running in browser
Desktop Applications: Installed applications where client secret protection is difficult
Progressive Web Apps (PWAs): Web applications with app-like functionality
CLI Tools: Command-line applications requiring user authentication
IoT Devices: Devices with limited security capabilities
Sample Implementation Code Snippets
Generating Code Verifier and Challenge (JavaScript):
// Generate random code_verifier const generateCodeVerifier = () => { const array = new Uint8Array(32); crypto.getRandomValues(array); return base64url.encode(array); }
// Create code_challenge const createCodeChallenge = async (verifier) => { const encoder = new TextEncoder(); const data = encoder.encode(verifier); const digest = await crypto.subtle.digest('SHA-256', data); return base64url.encode(new Uint8Array(digest)); }
Conclusion
PKCE provides a robust security enhancement to the OAuth 2.0 authorization code flow, particularly for public clients that cannot maintain client secrecy. By adding the code verifier/challenge mechanism, it effectively prevents authorization code interception attacks while maintaining the benefits of the authorization code flow. Its adoption is now considered a best practice for mobile applications, SPAs, and other public clients, and is recommended by security standards such as OAuth 2.0 Security Best Current Practice and OpenID Connect.
Comments
to like and join the conversation.