Skip to main content
Back to Blog
30 August 202515 min read

Keycloak: Open-Source Identity and Access Management

KeycloakIAMSecuritySSOOAuth2

Implementing enterprise IAM with Keycloak. User federation, SSO, OAuth2/OIDC, SAML, and integration with modern applications and microservices.


Keycloak: Open-Source Identity and Access Management

Keycloak is an open-source Identity and Access Management (IAM) solution that provides single sign-on, user federation, and identity brokering. It supports standard protocols like OAuth 2.0, OpenID Connect, and SAML 2.0, making it ideal for securing modern applications and microservices.

Keycloak Architecture

Core Components

Keycloak Architecture:
┌──────────────────────────────────────────────────────────────┐
│                        Keycloak Server                        │
│  ┌────────────────────────────────────────────────────────┐  │
│  │                       Realms                            │  │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │  │
│  │  │   Master     │  │   Company    │  │   Partner    │  │  │
│  │  │   Realm      │  │   Realm      │  │   Realm      │  │  │
│  │  └──────────────┘  └──────────────┘  └──────────────┘  │  │
│  └────────────────────────────────────────────────────────┘  │
│                                                               │
│  ┌───────────────┐  ┌───────────────┐  ┌───────────────┐    │
│  │    Users &    │  │    Clients    │  │   Identity    │    │
│  │    Groups     │  │ (Applications)│  │   Providers   │    │
│  └───────────────┘  └───────────────┘  └───────────────┘    │
│                                                               │
│  ┌───────────────┐  ┌───────────────┐  ┌───────────────┐    │
│  │    Roles &    │  │   Sessions    │  │    Events &   │    │
│  │  Permissions  │  │   & Tokens    │  │    Auditing   │    │
│  └───────────────┘  └───────────────┘  └───────────────┘    │
└──────────────────────────────────────────────────────────────┘

Key Concepts

ConceptDescription
RealmIsolated tenant with its own users, clients, and settings
ClientApplication that uses Keycloak for authentication
User FederationConnect external user directories (LDAP, Active Directory)
Identity ProviderExternal login providers (Google, SAML IdP)
RolesDefine permissions that can be assigned to users
GroupsOrganize users and assign role mappings

Deployment

Kubernetes Deployment

# keycloak-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: keycloak namespace: identity spec: replicas: 2 selector: matchLabels: app: keycloak template: metadata: labels: app: keycloak spec: containers: - name: keycloak image: quay.io/keycloak/keycloak:24.0 args: - start - --hostname=auth.example.com - --proxy-headers=xforwarded - --http-enabled=true env: - name: KC_DB value: postgres - name: KC_DB_URL value: jdbc:postgresql://postgres:5432/keycloak - name: KC_DB_USERNAME valueFrom: secretKeyRef: name: keycloak-db key: username - name: KC_DB_PASSWORD valueFrom: secretKeyRef: name: keycloak-db key: password - name: KC_HEALTH_ENABLED value: "true" - name: KC_METRICS_ENABLED value: "true" - name: KEYCLOAK_ADMIN valueFrom: secretKeyRef: name: keycloak-admin key: username - name: KEYCLOAK_ADMIN_PASSWORD valueFrom: secretKeyRef: name: keycloak-admin key: password ports: - name: http containerPort: 8080 - name: management containerPort: 9000 livenessProbe: httpGet: path: /health/live port: management initialDelaySeconds: 60 readinessProbe: httpGet: path: /health/ready port: management initialDelaySeconds: 30 resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "2Gi" cpu: "2000m" --- apiVersion: v1 kind: Service metadata: name: keycloak namespace: identity spec: selector: app: keycloak ports: - name: http port: 8080 targetPort: 8080

Terraform Configuration

# keycloak.tf terraform { required_providers { keycloak = { source = "mrparkers/keycloak" version = "~> 4.0" } } } provider "keycloak" { client_id = "admin-cli" username = var.keycloak_admin_username password = var.keycloak_admin_password url = var.keycloak_url } # Create realm resource "keycloak_realm" "main" { realm = var.realm_name enabled = true display_name = "${var.project} Realm" # Login settings login_with_email_allowed = true duplicate_emails_allowed = false reset_password_allowed = true remember_me = true registration_allowed = var.allow_registration # Token settings access_token_lifespan = "1h" refresh_token_max_reuse = 0 sso_session_idle_timeout = "30m" sso_session_max_lifespan = "10h" # Password policy password_policy = "length(12) and upperCase(1) and lowerCase(1) and digits(1) and specialChars(1) and notUsername" # Brute force protection brute_force_detection { permanent_lockout = false max_login_failures = 5 wait_increment_seconds = 60 quick_login_check_milli_seconds = 1000 minimum_quick_login_wait_seconds = 60 max_failure_wait_seconds = 900 failure_reset_time_seconds = 43200 } # Email settings smtp_server { host = var.smtp_host port = var.smtp_port from = var.smtp_from ssl = true auth { username = var.smtp_username password = var.smtp_password } } } # Create client for web application resource "keycloak_openid_client" "web" { realm_id = keycloak_realm.main.id client_id = "${var.project}-web" name = "${var.project} Web Application" enabled = true access_type = "PUBLIC" standard_flow_enabled = true implicit_flow_enabled = false direct_access_grants_enabled = false valid_redirect_uris = var.web_redirect_uris web_origins = var.web_origins login_theme = var.login_theme } # Create client for API resource "keycloak_openid_client" "api" { realm_id = keycloak_realm.main.id client_id = "${var.project}-api" name = "${var.project} API" enabled = true access_type = "CONFIDENTIAL" service_accounts_enabled = true standard_flow_enabled = false direct_access_grants_enabled = false valid_redirect_uris = [] } # Create roles resource "keycloak_role" "admin" { realm_id = keycloak_realm.main.id name = "admin" } resource "keycloak_role" "user" { realm_id = keycloak_realm.main.id name = "user" } # Default role for new users resource "keycloak_default_roles" "default" { realm_id = keycloak_realm.main.id default_roles = [keycloak_role.user.name] }

Client Integration

React/Next.js Integration

// lib/keycloak.ts import Keycloak from 'keycloak-js'; const keycloakConfig = { url: process.env.NEXT_PUBLIC_KEYCLOAK_URL!, realm: process.env.NEXT_PUBLIC_KEYCLOAK_REALM!, clientId: process.env.NEXT_PUBLIC_KEYCLOAK_CLIENT_ID! }; let keycloak: Keycloak | null = null; export const getKeycloak = (): Keycloak => { if (!keycloak) { keycloak = new Keycloak(keycloakConfig); } return keycloak; }; // auth-context.tsx import { createContext, useContext, useEffect, useState, ReactNode } from 'react'; import Keycloak from 'keycloak-js'; import { getKeycloak } from './keycloak'; interface AuthContextType { isAuthenticated: boolean; isLoading: boolean; token: string | null; user: KeycloakUser | null; login: () => void; logout: () => void; hasRole: (role: string) => boolean; } interface KeycloakUser { id: string; email: string; name: string; roles: string[]; } const AuthContext = createContext<AuthContextType | null>(null); export const AuthProvider = ({ children }: { children: ReactNode }) => { const [keycloak, setKeycloak] = useState<Keycloak | null>(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { const kc = getKeycloak(); kc.init({ onLoad: 'check-sso', silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html', pkceMethod: 'S256' }) .then((authenticated) => { setKeycloak(kc); setIsLoading(false); if (authenticated) { // Set up token refresh setInterval(() => { kc.updateToken(70).catch(() => { console.log('Failed to refresh token'); }); }, 60000); } }) .catch((error) => { console.error('Keycloak init failed:', error); setIsLoading(false); }); }, []); const login = () => { keycloak?.login(); }; const logout = () => { keycloak?.logout({ redirectUri: window.location.origin }); }; const hasRole = (role: string): boolean => { return keycloak?.hasRealmRole(role) || false; }; const user: KeycloakUser | null = keycloak?.tokenParsed ? { id: keycloak.tokenParsed.sub!, email: keycloak.tokenParsed.email, name: keycloak.tokenParsed.name, roles: keycloak.tokenParsed.realm_access?.roles || [] } : null; return ( <AuthContext.Provider value={{ isAuthenticated: !!keycloak?.authenticated, isLoading, token: keycloak?.token || null, user, login, logout, hasRole }} > {children} </AuthContext.Provider> ); }; export const useAuth = () => { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within an AuthProvider'); } return context; };

Backend Token Validation

// middleware/auth.ts import jwt, { JwtPayload } from 'jsonwebtoken'; import jwksClient from 'jwks-rsa'; const client = jwksClient({ jwksUri: `${process.env.KEYCLOAK_URL}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/certs`, cache: true, cacheMaxAge: 600000 }); const getKey = (header: jwt.JwtHeader, callback: jwt.SigningKeyCallback) => { client.getSigningKey(header.kid, (err, key) => { if (err) { callback(err); } else { const signingKey = key?.getPublicKey(); callback(null, signingKey); } }); }; interface TokenPayload extends JwtPayload { realm_access?: { roles: string[] }; resource_access?: Record<string, { roles: string[] }>; email: string; preferred_username: string; } export const validateToken = (token: string): Promise<TokenPayload> => { return new Promise((resolve, reject) => { jwt.verify( token, getKey, { issuer: `${process.env.KEYCLOAK_URL}/realms/${process.env.KEYCLOAK_REALM}`, algorithms: ['RS256'] }, (err, decoded) => { if (err) { reject(err); } else { resolve(decoded as TokenPayload); } } ); }); }; // Express middleware export const requireAuth = async ( req: Request, res: Response, next: NextFunction ) => { const authHeader = req.headers.authorization; if (!authHeader?.startsWith('Bearer ')) { return res.status(401).json({ error: 'Missing or invalid authorization header' }); } const token = authHeader.substring(7); try { const decoded = await validateToken(token); req.user = { id: decoded.sub!, email: decoded.email, roles: decoded.realm_access?.roles || [] }; next(); } catch (error) { return res.status(401).json({ error: 'Invalid token' }); } }; export const requireRole = (...roles: string[]) => { return (req: Request, res: Response, next: NextFunction) => { const userRoles = req.user?.roles || []; const hasRole = roles.some(role => userRoles.includes(role)); if (!hasRole) { return res.status(403).json({ error: 'Insufficient permissions' }); } next(); }; };

User Federation

LDAP Configuration

# ldap.tf resource "keycloak_ldap_user_federation" "ldap" { realm_id = keycloak_realm.main.id name = "Active Directory" enabled = true priority = 1 # Connection settings connection_url = var.ldap_url bind_dn = var.ldap_bind_dn bind_credential = var.ldap_bind_password users_dn = var.ldap_users_dn connection_timeout = "5s" read_timeout = "10s" # Sync settings batch_size_for_sync = 1000 full_sync_period = 604800 # Weekly changed_sync_period = 86400 # Daily import_enabled = true sync_registrations = false # User object settings username_ldap_attribute = "sAMAccountName" rdn_ldap_attribute = "cn" uuid_ldap_attribute = "objectGUID" user_object_classes = ["person", "organizationalPerson", "user"] # Edit mode edit_mode = "READ_ONLY" } # Map LDAP attributes to Keycloak resource "keycloak_ldap_user_attribute_mapper" "email" { realm_id = keycloak_realm.main.id ldap_user_federation_id = keycloak_ldap_user_federation.ldap.id name = "email" user_model_attribute = "email" ldap_attribute = "mail" read_only = true always_read_value_from_ldap = true } resource "keycloak_ldap_user_attribute_mapper" "first_name" { realm_id = keycloak_realm.main.id ldap_user_federation_id = keycloak_ldap_user_federation.ldap.id name = "first name" user_model_attribute = "firstName" ldap_attribute = "givenName" read_only = true } resource "keycloak_ldap_user_attribute_mapper" "last_name" { realm_id = keycloak_realm.main.id ldap_user_federation_id = keycloak_ldap_user_federation.ldap.id name = "last name" user_model_attribute = "lastName" ldap_attribute = "sn" read_only = true } # Map LDAP groups resource "keycloak_ldap_group_mapper" "groups" { realm_id = keycloak_realm.main.id ldap_user_federation_id = keycloak_ldap_user_federation.ldap.id name = "groups" ldap_groups_dn = var.ldap_groups_dn group_name_ldap_attribute = "cn" group_object_classes = ["group"] membership_ldap_attribute = "member" membership_user_ldap_attribute = "dn" mode = "READ_ONLY" user_roles_retrieve_strategy = "LOAD_GROUPS_BY_MEMBER_ATTRIBUTE" drop_non_existing_groups_during_sync = false }

Custom Themes

Theme Structure

themes/custom-theme/
├── login/
│   ├── resources/
│   │   ├── css/
│   │   │   └── login.css
│   │   └── img/
│   │       └── logo.svg
│   ├── messages/
│   │   └── messages_en.properties
│   └── theme.properties
├── account/
│   └── theme.properties
└── email/
    ├── messages/
    │   └── messages_en.properties
    └── theme.properties

Login Theme CSS

/* themes/custom-theme/login/resources/css/login.css */ :root { --pf-global--primary-color--100: #0066cc; --pf-global--BackgroundColor--100: #ffffff; } .login-pf body { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } #kc-header-wrapper { text-align: center; padding: 40px 0; } #kc-logo { width: 200px; height: auto; } .card-pf { border-radius: 12px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); max-width: 400px; margin: 0 auto; } #kc-form-login { padding: 30px; } .pf-c-button.pf-m-primary { background-color: var(--pf-global--primary-color--100); border-radius: 6px; width: 100%; }

Key Takeaways

  1. Use realms for tenancy: Each realm provides complete isolation

  2. Secure token handling: Use PKCE for public clients, validate tokens properly

  3. Federation for enterprise: Connect LDAP/AD instead of syncing users

  4. Role-based access: Define granular roles and map to groups

  5. High availability: Deploy multiple instances with shared database

  6. Custom themes: Brand the login experience for your organisation

  7. Monitor events: Enable event logging for security auditing

  8. Regular updates: Keep Keycloak updated for security patches

Keycloak provides enterprise-grade IAM capabilities without vendor lock-in. Proper configuration of federation, roles, and security settings enables secure, scalable identity management.

Share this article