Managing authentication and authorization in modern web applications can quickly become a maintenance nightmare. Between handling password resets, multi-factor authentication (MFA), social logins, and role-based access control (RBAC), building an in-house solution consumes valuable time that could be spent on core business logic.
Enter Keycloak: an open-source Identity and Access Management (IAM) solution maintained by Red Hat. Keycloak acts as a centralized authentication server that supports industry standards like OAuth 2.0, OpenID Connect (OIDC), and SAML 2.0.
Whether you are building a microservices architecture or a single-page application (SPA), understanding Keycloak's foundation is crucial. In this guide, we will break down the essential Keycloak concepts and look at how to integrate it into your application workflow.
Core Concepts: Demystifying Keycloak Terminology
To work effectively with Keycloak, you need to understand its core building blocks. Let's look at how Keycloak organizes users, permissions, and applications.
1. Realms
A Realm is a management space inside Keycloak that manages a set of users, credentials, roles, and clients. It provides an isolated domain where entities cannot access resources in another realm.
- Master Realm: Created by default. It contains the administrative accounts used to manage Keycloak itself. Rule of thumb: Never use the Master realm for managing your application users.
-
Custom Realms: You create custom realms (e.g.,
company-dev,my-app-production) for your actual applications.
2. Clients
In Keycloak terminology, a Client is an entity that requests Keycloak to authenticate a user. Clients can be front-end web apps, mobile apps, backend REST APIs, or third-party services.
Keycloak categorizes clients based on their ability to keep credentials secret:
- Public Clients: Cannot hold client secrets safely (e.g., React/Vue SPAs, React Native mobile apps). They must use the Authorization Code Flow with PKCE (Proof Key for Code Exchange).
- Confidential Clients: Backend applications capable of maintaining a client secret securely (e.g., Node.js Express, Spring Boot, Ruby on Rails apps).
- Bearer-only Clients: Backend APIs that do not initiate logins themselves; they only verify incoming HTTP bearer tokens (JWTs).
3. Users, Groups, and Roles
- Users: Individual entities that log into your applications.
- Groups: Collections of users. Roles assigned to a group are automatically inherited by its members.
-
Roles: Permissions granted to users. Keycloak supports two levels of roles:
-
Realm Roles: Global permissions valid across all clients inside the realm (e.g.,
global-admin). -
Client Roles: Permissions specific to a particular client (e.g.,
reports-service-viewer).
-
Realm Roles: Global permissions valid across all clients inside the realm (e.g.,
The Authentication Flow in Action
When using OpenID Connect (OIDC) with Keycloak, a typical login flow for a Single Page Application with a Backend API looks like this:
- Login Request: User clicks "Login" in the React/Vue frontend.
- Redirect to Keycloak: Frontend redirects the user to Keycloak's login page.
- User Authenticates: User enters credentials (and MFA if configured).
- Authorization Code: Keycloak redirects back to the frontend with a short-lived authorization code.
- Token Exchange: Frontend exchanges the code (and PKCE verifier) for tokens via Keycloak's token endpoint.
- Access Token Issue: Keycloak returns an Access Token (JWT), ID Token, and Refresh Token.
-
API Requests: Frontend attaches the Access Token in the
Authorization: Bearer <token>header on backend requests. - Token Validation: The backend API verifies the JWT's signature and claims without calling Keycloak every time.
Quick Start: Running Keycloak with Docker
The fastest way to experiment with Keycloak locally is via Docker:
docker run -d \
--name keycloak_dev \
-p 8080:8080 \
-e KEYCLOAK_ADMIN=admin \
-e KEYCLOAK_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:24.0.0 \
start-dev
Once running, navigate to http://localhost:8080, access the Admin Console, log in with admin/admin, and create a new realm (e.g., dev-realm).
Practical Example: Validating Keycloak JWTs in a Node.js Backend
Backend services need to verify the JWT tokens sent by client applications. Instead of querying Keycloak on every incoming HTTP request, backend APIs download Keycloak's public keys via JWKS (JSON Web Key Set) to verify signatures locally.
Here is how you can implement token verification in a Node.js / Express application using jsonwebtoken and jwks-rsa:
const express = require('express');
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
const app = express();
const KEYCLOAK_URL = 'http://localhost:8080';
const REALM_NAME = 'dev-realm';
// Configure JWKS client to fetch public keys from Keycloak
const client = jwksClient({
jwksUri: `${KEYCLOAK_URL}/realms/${REALM_NAME}/protocol/openid-connect/certs`
});
// Helper to get signing key from JWKS
function getKey(header, callback) {
client.getSigningKey(header.kid, function(err, key) {
if (err) return callback(err);
const signingKey = key.getPublicKey();
callback(null, signingKey);
});
}
// Authentication Middleware
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.sendStatus(401);
jwt.verify(token, getKey, {
issuer: `${KEYCLOAK_URL}/realms/${REALM_NAME}`,
algorithms: ['RS256']
}, (err, decoded) => {
if (err) {
console.error('Token verification failed:', err.message);
return res.sendStatus(403);
}
// Attach decoded user payload to request
req.user = decoded;
next();
});
}
// Protected endpoint
app.get('/api/protected', authenticateToken, (req, res) => {
res.json({
message: "Access granted to secure resource!",
user: req.user.preferred_username,
roles: req.user.realm_access?.roles || []
});
});
app.listen(3000, () => console.log('Backend running on port 3000'));
Inspecting User Roles in the JWT
When Keycloak issues an access token, it includes realm and client roles inside the payload:
{
"exp": 1710000000,
"iss": "http://localhost:8080/realms/dev-realm",
"preferred_username": "johndoe",
"email": "john@example.com",
"realm_access": {
"roles": ["user", "developer"]
},
"resource_access": {
"my-backend-api": {
"roles": ["read:reports"]
}
}
}
Your backend middleware can inspect realm_access.roles or resource_access.<client>.roles to enforce fine-grained access policies.
Essential Best Practices for Production
If you are planning to deploy Keycloak to production, keep these operational rules in mind:
- Always Use HTTPS: OAuth tokens are sensitive authorization credentials. Transmitting them over unencrypted HTTP exposes your app to man-in-the-middle (MitM) attacks.
- Keep Token Lifetimes Short: Set Access Token expiration to short durations (e.g., 5 to 15 minutes). Rely on Refresh Tokens to get new access tokens seamlessly.
- Use Realm Export/Import for CI/CD: Keycloak allows exporting realm configurations to JSON format. Keep your realm settings in source control and import them during deployment pipelines rather than configuring production realms manually through the UI.
- Never Store Secrets in Front-end Code: Always use PKCE for browser and mobile applications. Client secrets belong strictly on server-side code.
- Use an External Database: Keycloak ships with an embedded H2 database by default. Never use H2 in production; connect Keycloak to a resilient PostgreSQL or MySQL cluster instead.
Conclusion
Keycloak takes the heavy lifting out of application security by providing a battle-tested, standard-compliant authorization service. By mastering basic concepts like Realms, Clients, Scopes, and JWKS-based token verification, you can easily integrate secure single sign-on and role management into any modern software architecture.
Have you used Keycloak in production, or are you exploring it for your next project? Let me know your thoughts and questions in the comments below!
Top comments (0)