Passport strategy for authenticating with Keycloak using OAuth2/OIDC.
- Why I Did This
- What's New
- Installation
- Usage
- Development Environment
- Testing
- Understanding PKCE
- Additional Information
- License
The original passport-keycloak-oauth2-oidc wasn't up to date. I modernized it with improved PKCE support, standalone client examples, and TypeScript support. Check out my blog for tutorials: blog.brakmic.com.
- TypeScript Only: The project is now TypeScript-based, with no JavaScript code.
- PKCE Support: Full Proof Key for Code Exchange (PKCE) implementation for public clients.
- Standalone Public Client: A single self-contained example demonstrating PKCE.
- Keycloak via Docker Compose: Simplified container-based setup.
npm install passport-keycloak-oauth2-oidc-portable
pnpm add passport-keycloak-oauth2-oidc-portable
yarn add passport-keycloak-oauth2-oidc-portable
- Configure your Keycloak realm
- Create a client (public or confidential)
- Set valid redirect URIs
- Enable PKCE if using public client
import passport from 'passport';
import KeycloakStrategy from 'passport-keycloak-oauth2-oidc-portable';
passport.use(new KeycloakStrategy({
clientID: 'your-client-id',
realm: 'your-realm',
publicClient: true,
authServerURL: 'http://localhost:3000',
callbackURL: 'http://localhost:3002/auth/callback',
pkce: true,
state: true
},
(accessToken, refreshToken, profile, done) => {
return done(null, profile);
}
));
import express from 'express';
import session from 'express-session';
import passport from 'passport';
import KeycloakStrategy from 'passport-keycloak-oauth2-oidc-portable';
const app = express();
app.use(session({
secret: 'your-secret',
resave: false,
saveUninitialized: false,
cookie: {
secure: false,
maxAge: 1000 * 60 * 60 * 24
}
}));
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((user, done) => done(null, user));
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 });
}));
app.get('/auth/keycloak', passport.authenticate('keycloak'));
app.get('/auth/callback',
passport.authenticate('keycloak', {
successRedirect: '/profile',
failureRedirect: '/login',
failureFlash: true
})
);
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);
Service | Internal URL | External URL | Purpose | Ports | Networks |
---|---|---|---|---|---|
Keycloak | http://testcloak:8080 | http://localhost:3000 | Auth Server | 8080, 9000 | devnetwork |
NGINX | http://test-proxy:8080 | http://localhost:3000 | Proxy | 3000, 8080 | devnetwork |
E2E Server | http://e2e-server:4000 | N/A | Protected API | 4000 | devnetwork |
Public Client | http://localhost:3002 | http://localhost:3002 | Example App | 3002 | host |
Script | Purpose | Usage |
---|---|---|
setup:test-env |
Start test environment | yarn setup:test-env |
setup:test-env:force |
Force restart environment | yarn setup:test-env:force |
start:public-client |
Run example client | yarn start:public-client |
test:integration |
Run integration tests | yarn test:integration |
test:e2e |
Run E2E tests | yarn test:e2e |
test:unit |
Run unit tests | yarn test:unit |
test |
Run all tests | yarn test |
The test environment consists of:
- Keycloak server (authentication)
- NGINX proxy (routing)
- E2E server (protected resources)
- Test realm with predefined users
# Start environment
yarn setup:test-env
# Run specific tests
yarn test:unit
yarn test:integration
yarn test:e2e
# Run all tests
yarn test
PKCE (Proof Key for Code Exchange) is a security extension for OAuth 2.0 used with public clients. The implementation:
- Generates a code verifier (random string)
- Creates a code challenge (SHA-256 hash of verifier)
- Sends challenge with auth request
- Sends verifier with token request
// PKCE Implementation Example
const { code_verifier, code_challenge } = generatePkcePair();
// code_verifier is stored in session
// code_challenge is sent with authorization request
- Keycloak Admin: http://localhost:3000/admin
- Public Client: http://localhost:3002
- E2E Server: http://localhost:4000
- Keycloak Admin: admin/admin
- Test User: test-user/password