A simple, secure Keycloak authentication library for Koa applications with TypeScript support.
npm install keycloak-koa
import Koa from 'koa'
import Router from 'koa-router'
import koaCookie from 'koa-cookie'
import keycloak from 'keycloak-koa'
const app = new Koa()
app.use(koaCookie())
const router = new Router()
// Initialize Keycloak
const auth = keycloak({
keycloakUrl: 'https://auth.example.com/realms/my-realm',
clientId: 'my-client',
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
})
// Protect routes
router.get('/api/profile', auth.middleware.extractJwtToken, async (ctx) => {
ctx.body = { user: ctx.state.user }
})
app.use(router.routes())
app.listen(3000)
Here's a complete example showing all the endpoints you'll need:
import Koa from 'koa'
import Router from 'koa-router'
import koaBody from 'koa-body'
import koaCookie from 'koa-cookie'
import session from 'koa-session'
import keycloak from 'keycloak-koa'
const app = new Koa()
app.keys = ['your-secret-key']
app.use(koaBody())
app.use(koaCookie())
app.use(session(app))
const router = new Router()
// 1. Initialize Keycloak
const auth = keycloak({
keycloakUrl: 'https://auth.example.com/realms/my-realm',
clientId: 'my-client',
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
})
// 2. Login endpoint - redirect users to Keycloak
router.get('/login', async (ctx) => {
const redirectUri = `${ctx.protocol}://${ctx.host}/auth/callback`
// Generate a random state for CSRF protection
const state = auth.generateRandomState()
ctx.session.authState = state // Store in session
// Use the helper to generate the auth URL
const authUrl = auth.getAuthUrl(redirectUri, { state })
ctx.redirect(authUrl)
})
// 3. Callback endpoint - handle the code from Keycloak
router.get('/auth/callback', async (ctx) => {
try {
const { code, state } = ctx.query
// Verify state parameter to prevent CSRF attacks
if (state !== ctx.session.authState) {
return ctx.redirect('/login?error=invalid_state')
}
const redirectUri = `${ctx.protocol}://${ctx.host}/auth/callback`
// Exchange code for tokens and set cookies
const { user } = await auth.handleTokenExchange(
code as string,
redirectUri,
ctx
)
// Redirect to the application
ctx.redirect('/dashboard')
} catch (error) {
console.error('Authentication error:', error)
ctx.redirect('/login?error=auth_failed')
}
})
// 4. Protected API routes
router.get('/api/profile', auth.middleware.extractJwtToken, async (ctx) => {
ctx.body = {
user: ctx.state.user,
message: 'This is a protected endpoint',
}
})
// 5. Logout endpoint
router.get('/logout', async (ctx) => {
auth.logout(ctx)
// Use the helper to generate the logout URL
const redirectUri = `${ctx.protocol}://${ctx.host}`
const logoutUrl = auth.getLogoutUrl(redirectUri)
ctx.redirect(logoutUrl)
})
app.use(router.routes())
app.use(router.allowedMethods())
app.listen(3000, () => {
console.log('Server running on http://localhost:3000')
})
Initializes the Keycloak integration with your configuration.
Options:
keycloakUrl
(required): Keycloak server URL with realmclientId
(required): Your application's client IDclientSecret
(required for confidential clients): Client secret
Returns an object with:
middleware.extractJwtToken
: Middleware to protect routeshandleTokenExchange(code, redirectUri, res)
: Helper to exchange code for tokenslogout(res)
: Helper to clear auth cookiesgetAuthUrl(redirectUri, options)
: Helper to generate Keycloak authorization URLgetLogoutUrl(redirectUri, options)
: Helper to generate Keycloak logout URLgenerateRandomState(length)
: Helper to generate random state for CSRF protection
These functions are also available directly from the module:
Generates a Keycloak authorization URL.
const { getAuthUrl } = require('keycloak-koa')
const authUrl = getAuthUrl({
keycloakUrl: 'https://auth.example.com/realms/my-realm',
clientId: 'my-client',
redirectUri: 'https://myapp.com/callback',
scope: 'openid profile email',
state: 'random-state-string',
prompt: 'login', // Force login even if already authenticated
})
Generates a Keycloak logout URL.
const { getLogoutUrl } = require('keycloak-koa')
const logoutUrl = getLogoutUrl({
keycloakUrl: 'https://auth.example.com/realms/my-realm',
redirectUri: 'https://myapp.com/logged-out',
idTokenHint: 'optional-id-token',
})
Generates a random string for CSRF protection.
const { generateRandomState } = require('keycloak-koa')
const state = generateRandomState(32) // Default length is 32
- HTTP-only cookies prevent JavaScript access to tokens
- Automatic token verification using Keycloak's JWKS endpoint
- Protection against token tampering and replay attacks
- Secure cookie settings with sameSite and secure flags
MIT