Skip to content

Latest commit

 

History

History
248 lines (198 loc) · 12.7 KB

File metadata and controls

248 lines (198 loc) · 12.7 KB

PKCE (Proof Key for Code Exchange)

  1. Introduction
  2. Understanding PKCE
  3. PKCE Flow Overview
  4. Implementation
  5. Security Considerations
  6. Best Practices
  7. Complete Implementation Example
  8. References

Introduction

In modern web applications, securing authentication and authorization processes is paramount. Proof Key for Code Exchange (PKCE) is an extension to the OAuth 2.0 authorization framework that enhances security, especially for public clients unable to securely store client secrets. This document delves into PKCE, elucidates its workflow, and demonstrates its integration with Keycloak using a custom Passport.js strategy written in TypeScript.

Understanding PKCE

What is PKCE?

Proof Key for Code Exchange (PKCE), pronounced "pixy," is an OAuth 2.0 extension designed to prevent authorization code interception attacks. Initially crafted for mobile and native applications, PKCE is now recommended for all types of OAuth clients, including single-page applications and other public clients.

Key Components of PKCE

  1. Code Verifier: A high-entropy cryptographic random string generated by the client. It serves as a secret used to validate the authorization request.
  2. Code Challenge: A transformation of the code verifier, typically using SHA-256 hashing followed by Base64 URL encoding. This is sent to the authorization server during the initial authorization request.
  3. Authorization Code: A temporary code received from the authorization server upon successful user authentication. It is exchanged for access tokens.
  4. Code Challenge Method: Specifies the method used to derive the code challenge from the code verifier. Commonly, S256 (SHA-256) is used.

PKCE Flow Overview

+--------+                                +-------------------+                                +-----------------+
|        |                                |                   |                                |                 |
|  User  |                                |   Client App      |                                | Keycloak Auth   |
|Browser |                                |                   |                                |    Server       |
|        |                                |                   |                                |                 |
+---+----+                                +---------+---------+                                +--------+--------+
    |                                             |                                                     |
    | (A) User initiates login                    |                                                     |
    |-------------------------------------------->|                                                     |
    |                                             |                                                     |
    |                                             | (B) Generate code_verifier & code_challenge         |
    |                                             |---------------------------------------------------> |
    |                                             |                                                     |
    |                                             | (C) Redirect to Keycloak with code_challenge        |
    |                                             |<--------------------------------------------------- |
    |                                             |                                                     |
    | (D) User authenticates & consents           |                                                     |
    |-------------------------------------------->|                                                     |
    |                                             |                                                     |
    |                                             | (E) Keycloak redirects to callback with code        |
    |                                             |---------------------------------------------------> |
    |                                             |                                                     |
    |                                             |                                                     |
    |                                             | (F) Client receives code & retrieves code_verifier  |
    |                                             |                                                     |
    |                                             | (G) Client requests tokens with code & code_verifier|
    |                                             |---------------------------------------------------> |
    |                                             |                                                     |
    |                                             | (H) Keycloak validates code_verifier & issues tokens|
    |                                             |<--------------------------------------------------- |
    |                                             |                                                     |
    |                                             | (I) Client receives access token                    |
    |                                             |                                                     |
    | (J) Access protected resources              |                                                     |
    |-------------------------------------------->|                                                     |
    |                                             |                                                     |
+--------+                                +---------+---------+                                +-----------------+
|        |                                |                   |                                |                 |
|Resource|                                |                   |                                |                 |
| Server |                                |                   |                                |                 |
|        |                                |                   |                                |                 |
+--------+                                +-------------------+                                +-----------------+

Legend:

  • (A): User initiates the login process on the Client Application.
  • (B): Client generates a code_verifier and derives a code_challenge using SHA-256.
  • (C): Client redirects the user's browser to Keycloak's authorization endpoint, including the code_challenge and specifying the code_challenge_method as S256.
  • (D): User authenticates with Keycloak and consents to the requested scopes.
  • (E): Upon successful authentication, Keycloak redirects the browser back to the Client's callback URL with an authorization code.
  • (F): Client receives the authorization code and retrieves the previously stored code_verifier from the session.
  • (G): Client sends a token request to Keycloak's token endpoint, including the authorization code and the code_verifier.
  • (H): Keycloak verifies that the code_verifier matches the code_challenge and, upon validation, issues access (and optionally refresh) tokens.
  • (I): Client receives the access token from Keycloak.
  • (J): Client uses the access token to access protected resources on the Resource Server.

This diagram illustrates each step of the PKCE flow, highlighting the interactions between the user's browser, the client application, Keycloak's authorization server, and the resource server. By following this sequence, PKCE ensures a secure exchange of authorization codes and access tokens, mitigating potential interception attacks.

Integrating PKCE with Keycloak and Passport.js

Implementing PKCE with Keycloak involves configuring both the client application and the authentication strategy used to communicate with Keycloak. In this setup, we utilize a custom Passport.js strategy, KeycloakStrategy, built upon the passport-oauth2 library, to handle the OAuth 2.0 flow with PKCE.

KeycloakStrategy

KeycloakStrategy extends OAuth2Strategy from passport-oauth2 to incorporate PKCE-specific parameters into the authorization and token requests. It handles:

  • Authorization Parameters: Injecting code_challenge and code_challenge_method into authorization requests.
  • Token Parameters: Including the code_verifier during the token exchange.
  • User Profile Retrieval: Fetching and parsing user information from Keycloak's userinfo endpoint.

Implementation

PKCE is automatically handled by KeycloakStrategy when enabled in the configuration:

import KeycloakStrategy from 'passport-keycloak-oauth2-oidc-portable';

passport.use(new KeycloakStrategy({
  clientID: 'test-client',
  realm: 'TestRealm',
  publicClient: true,
  authServerURL: 'http://localhost:3000',
  callbackURL: 'http://localhost:3002/auth/callback',
  pkce: true,  // Enable PKCE
  state: true  // Use StateStore
}, callback));

Session Configuration

PKCE requires session support for storing the code verifier:

import session from 'express-session';

app.use(session({
  secret: 'your-secret',
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    maxAge: 24 * 60 * 60 * 1000
  }
}));

Authentication Routes

// Initialize authentication
app.get('/auth/keycloak', 
  passport.authenticate('keycloak')
);

// Handle callback with PKCE verification
app.get('/auth/callback',
  passport.authenticate('keycloak', {
    successRedirect: '/profile',
    failureRedirect: '/login'
  })
);

Advantages of Using PKCE

  1. Enhanced Security: Binds the authorization request to the client by requiring the correct code verifier during the token exchange.
  2. No Need for Client Secrets: Ideal for public clients that cannot securely store secrets, reducing the risk associated with exposed client credentials.
  3. Protection Against Interception Attacks: Prevents malicious actors from using intercepted authorization codes without the correct code verifier.
  4. Compliance with OAuth 2.0 Best Practices: Aligns with modern security recommendations, ensuring robust authentication flows.

When and Where to Use PKCE

PKCE is particularly beneficial in scenarios where:

  • Public Clients: Applications like single-page apps, mobile apps, or any client that cannot securely store a client secret.
  • High-Security Applications: Systems handling sensitive user data requiring stringent security measures.
  • Modern OAuth Implementations: Adopting the latest OAuth 2.0 standards to ensure compatibility and security.

Complete Implementation Example

import express from 'express';
import session from 'express-session';
import passport from 'passport';
import KeycloakStrategy from 'passport-keycloak-oauth2-oidc-portable';

const app = express();

// Session configuration
app.use(session({
  secret: 'your-secret',
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    maxAge: 24 * 60 * 60 * 1000
  }
}));

app.use(passport.initialize());
app.use(passport.session());

// Passport configuration
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((user, done) => done(null, user));

// Strategy setup with PKCE
passport.use(new KeycloakStrategy({
  clientID: 'test-client',
  realm: 'TestRealm',
  publicClient: true,
  authServerURL: 'http://localhost:3000',
  callbackURL: 'http://localhost:3002/auth/callback',
  pkce: true,
  state: true
}, (accessToken, refreshToken, profile, done) => {
  return done(null, { ...profile, accessToken });
}));

// Routes
app.get('/auth/keycloak', passport.authenticate('keycloak'));

app.get('/auth/callback',
  passport.authenticate('keycloak', {
    successRedirect: '/profile',
    failureRedirect: '/login'
  })
);

app.get('/profile',
  ensureAuthenticated,
  (req, res) => {
    res.json({ user: req.user });
  }
);

function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) return next();
  res.redirect('/auth/keycloak');
}

app.listen(3002);

References