Keycloak Identity and Access Management
Authentication and Authorization
1. Introduction to Keycloak
Keycloak is an open-source Identity and Access Management solution aimed at modern applications and services. It provides user federation, strong authentication, user management, fine-grained authorization, and more.
What is Keycloak?
Keycloak is a comprehensive identity management solution that provides Single Sign-On (SSO), Identity Brokering, Social Login, User Federation, and Authorization Services. It acts as an identity provider that authenticates users and provides identity tokens to applications.
Key Features:
- Single Sign-On (SSO): Users authenticate once and access multiple applications
- User Federation: Integrate with LDAP, Active Directory, and other providers
- Social Login: Support for Google, Facebook, GitHub, and other providers
- Authorization Services: Fine-grained permissions and policies
- Admin Console: Web-based administration interface
- Themes: Customizable login pages and admin console
Why Keycloak?
Benefits:
- Open Source and Free
- Standards Compliant (OAuth2, OpenID Connect, SAML)
- Easy Integration
- Scalable Architecture
- Extensible
Use Cases:
- Enterprise SSO Solutions
- Microservices Authentication
- Mobile App Authentication
- API Protection
- User Management
2. Core Concepts
Keycloak Architecture
graph TD
A[Users] --> B[Keycloak Server]
C[Clients/Applications] --> B
B --> D[Identity Providers]
B --> E[User Storage]
B --> F[Token Services]
F --> C
Main Components
graph LR
A[Realm] --> B[Users]
A --> C[Clients]
A --> D[Roles]
A --> E[Groups]
A --> F[Identity Providers]
B --> G[Credentials]
C --> H[Client Scopes]
D --> I[Permissions]
Core Concepts Explained:
- Realm: An isolated namespace for managing identities and applications
- User: An entity that can authenticate and access resources
- Client: An application that requests authentication and authorization
- Role: A permission or capability assigned to users or clients
- Group: A collection of users for easier management
- Identity Provider: External authentication sources (LDAP, AD, Social)
3. Installation and Setup
Docker Installation
# Pull Keycloak Docker image
docker pull quay.io/keycloak/keycloak:22.0.1
# Run Keycloak container
docker run -p 8080:8080 \
-e KEYCLOAK_ADMIN=admin \
-e KEYCLOAK_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:22.0.1 \
start-dev
# Access Keycloak admin console
# http://localhost:8080/admin
# Username: admin
# Password: admin
Docker Compose Setup
version: '3.8'
services:
keycloak:
image: quay.io/keycloak/keycloak:22.0.1
ports:
- "8080:8080"
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: password
command: start-dev
depends_on:
- postgres
postgres:
image: postgres:15
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Standalone Installation
# Download Keycloak
wget https://github.com/keycloak/keycloak/releases/download/22.0.1/keycloak-22.0.1.tar.gz
tar -xzf keycloak-22.0.1.tar.gz
cd keycloak-22.0.1
# Start Keycloak
bin/kc.sh start-dev
# Or on Windows
bin\kc.bat start-dev
Installation Options:
- Docker: Quick setup for development and testing
- Standalone: Full installation with customization options
- Kubernetes: Production deployment with Helm charts
- Cloud: Managed services (Red Hat SSO, AWS, etc.)
4. Realm Setup and Configuration
Creating a New Realm
graph TD
A[Login to Admin Console] --> B[Create Realm]
B --> C[Configure Realm Settings]
C --> D[Set Display Name]
C --> E[Configure Login Settings]
C --> F[Set Themes]
C --> G[Configure Tokens]
D --> H[Save Realm]
Realm Configuration Steps
- Login to Admin Console: Navigate to http://localhost:8080/admin
- Create Realm: Click "Add realm" and enter realm name
- Configure Settings: Set display name, enabled status
- Login Configuration: Enable/disable registration, reset password, remember me
- Themes: Select login, email, and admin themes
- Tokens: Configure access token lifespan, refresh tokens
Realm Settings JSON
{
"id": "my-realm",
"realm": "my-realm",
"displayName": "My Application Realm",
"enabled": true,
"registrationAllowed": true,
"registrationEmailAsUsername": false,
"rememberMe": true,
"verifyEmail": false,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"resetPasswordAllowed": true,
"editUsernameAllowed": false,
"sslRequired": "external",
"accessTokenLifespan": 300,
"ssoSessionIdleTimeout": 1800,
"ssoSessionMaxLifespan": 36000,
"offlineSessionIdleTimeout": 2592000,
"accessCodeLifespan": 60,
"accessCodeLifespanUserAction": 300,
"accessCodeLifespanLogin": 1800
}
Realm Import/Export
# Export realm
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/admin/realms/my-realm \
-o my-realm.json
# Import realm
curl -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @my-realm.json \
http://localhost:8080/admin/realms
5. Client Authentication
Client Types
graph LR
A[Clients] --> B[Public Clients]
A --> C[Confidential Clients]
A --> D[Bearer-only Clients]
B --> E[SPA Applications]
B --> F[Mobile Apps]
C --> G[Backend Services]
C --> H[Web Applications]
D --> I[API Services]
Creating a Confidential Client
# Using Keycloak Admin CLI
kcadm.sh config credentials --server http://localhost:8080 \
--realm master --user admin --password admin
kcadm.sh create clients -r my-realm -s clientId=my-backend-client \
-s enabled=true -s clientAuthenticatorType=client-secret \
-s secret=my-client-secret -s 'redirectUris=["http://localhost:3000/*"]'
Client Configuration
{
"clientId": "my-web-app",
"name": "My Web Application",
"description": "Frontend web application",
"enabled": true,
"clientAuthenticatorType": "client-secret",
"secret": "my-client-secret",
"redirectUris": [
"http://localhost:3000/*",
"https://myapp.com/*"
],
"webOrigins": [
"http://localhost:3000",
"https://myapp.com"
],
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": false,
"publicClient": false,
"protocol": "openid-connect",
"attributes": {
"saml.assertion.signature": "false",
"saml.force.post.binding": "false",
"saml.multivalued.roles": "false",
"saml.encrypt": "false",
"oauth2.device.authorization.grant.enabled": "false",
"backchannel.logout.revoke.offline.tokens": "false",
"use.refresh.tokens": "true",
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"client_credentials.use_refresh_token": "false",
"acr.loa.map": "{}",
"require.pushed.authorization.requests": "false",
"tls.client.certificate.bound.access.tokens": "false",
"display.on.consent.screen": "false",
"token.response.type.bearer.lower-case": "false"
}
}
Client Scopes
{
"name": "profile",
"description": "OpenID Connect built-in scope: profile",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "true",
"display.on.consent.screen": "true",
"consent.screen.text": "${profileScopeConsentText}"
},
"protocolMappers": [
{
"name": "full name",
"protocol": "openid-connect",
"protocolMapper": "oidc-full-name-mapper",
"consentRequired": false,
"config": {
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true"
}
}
]
}
6. User Management
User Creation
# Create user via Admin CLI
kcadm.sh create users -r my-realm -s username=john.doe \
-s enabled=true -s email=john.doe@example.com
# Set password
kcadm.sh update users/$USER_ID/reset-password -r my-realm \
-s type=password -s value=Password123 -s temporary=false -n
User Representation
{
"username": "john.doe",
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"enabled": true,
"emailVerified": true,
"createdTimestamp": 1634567890000,
"totp": false,
"credentials": [
{
"type": "password",
"value": "Password123",
"temporary": false
}
],
"requiredActions": [],
"federatedIdentities": [],
"attributes": {
"department": ["engineering"],
"employeeId": ["EMP001"]
},
"groups": ["/developers", "/backend-team"]
}
Bulk User Import
[
{
"username": "alice.smith",
"email": "alice.smith@example.com",
"firstName": "Alice",
"lastName": "Smith",
"enabled": true,
"credentials": [
{
"type": "password",
"value": "Password123"
}
],
"groups": ["/marketing"]
},
{
"username": "bob.johnson",
"email": "bob.johnson@example.com",
"firstName": "Bob",
"lastName": "Johnson",
"enabled": true,
"credentials": [
{
"type": "password",
"value": "Password123"
}
],
"groups": ["/sales"]
}
]
User Federation with LDAP
{
"name": "ldap-provider",
"providerId": "ldap",
"providerType": "org.keycloak.storage.UserStorageProvider",
"parentId": "my-realm",
"config": {
"priority": ["0"],
"enabled": ["true"],
"cachePolicy": ["DEFAULT"],
"evictionDay": [""],
"evictionHour": [""],
"evictionMinute": [""],
"maxLifespan": [""],
"batchSizeForSync": ["1000"],
"fullSyncPeriod": ["-1"],
"changedSyncPeriod": ["-1"],
"lastSync": ["0"],
"connectionUrl": ["ldap://ldap.example.com:389"],
"usersDn": ["ou=people,dc=example,dc=com"],
"bindDn": ["cn=admin,dc=example,dc=com"],
"bindCredential": ["admin-password"],
"searchScope": ["1"],
"useTruststoreSpi": ["ldapsOnly"],
"connectionPooling": ["true"],
"pagination": ["true"],
"allowKerberosAuthentication": ["false"],
"debug": ["false"],
"useKerberosForPasswordAuthentication": ["false"]
}
}
7. Roles and Permissions
Role Hierarchy
graph TD
A[Realm Roles] --> B[Administrator]
A --> C[User]
A --> D[Guest]
B --> E[Manage Users]
B --> F[Manage Clients]
C --> G[View Profile]
C --> H[Manage Account]
D --> I[View Public Content]
Creating Realm Roles
# Create realm role via Admin CLI
kcadm.sh create roles -r my-realm -s name=admin -s description="Administrator role"
# Create composite role
kcadm.sh create roles -r my-realm -s name=super-user \
-s description="Super user with multiple roles"
# Add child roles to composite role
kcadm.sh create roles-by-id/$ROLE_ID/composites -r my-realm \
-s id=$CHILD_ROLE_ID
Client Roles
{
"name": "app-admin",
"description": "Application administrator",
"composite": true,
"clientRole": true,
"containerId": "my-web-app",
"attributes": {},
"composites": {
"client": {
"my-web-app": [
"manage-users",
"view-reports",
"configure-settings"
]
}
}
}
Assigning Roles to Users
# Assign realm role to user
kcadm.sh create users/$USER_ID/role-mappings/realm -r my-realm \
-s name=admin -s id=$ROLE_ID
# Assign client role to user
kcadm.sh create users/$USER_ID/role-mappings/clients/$CLIENT_ID -r my-realm \
-s name=app-admin -s id=$CLIENT_ROLE_ID
Fine-Grained Authorization
{
"name": "customer-resource-policy",
"type": "resource",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"resources": "[\"Customer Resource\"]",
"applyPolicies": "[\"only-admin-policy\"]"
}
}
8. Spring Boot Integration
Maven Dependencies
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
</dependencies>
Application Properties
# Keycloak configuration
spring.security.oauth2.client.registration.keycloak.client-id=my-web-app
spring.security.oauth2.client.registration.keycloak.client-secret=my-client-secret
spring.security.oauth2.client.registration.keycloak.scope=openid,profile,email
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.redirect-uri={baseUrl}/login/oauth2/code/keycloak
spring.security.oauth2.client.provider.keycloak.issuer-uri=http://localhost:8080/realms/my-realm
spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/realms/my-realm
Security Configuration
package com.example.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.defaultSuccessUrl("/dashboard")
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtAuthenticationConverter())
)
)
.logout(logout -> logout
.logoutSuccessUrl("http://localhost:8080/realms/my-realm/protocol/openid-connect/logout?redirect_uri=http://localhost:8081/")
);
return http.build();
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
authoritiesConverter.setAuthorityPrefix("ROLE_");
authoritiesConverter.setAuthoritiesClaimName("realm_access.roles");
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
return converter;
}
}
Protected Controller
package com.example.controller;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/user/profile")
public String getUserProfile(@AuthenticationPrincipal OidcUser user) {
return "Hello " + user.getGivenName() + " " + user.getFamilyName() +
" (Email: " + user.getEmail() + ")";
}
@GetMapping("/admin/dashboard")
public String getAdminDashboard(Authentication authentication) {
return "Admin Dashboard - User: " + authentication.getName();
}
@PostMapping("/user/data")
public String saveUserData(@RequestBody String data, Authentication authentication) {
// Check user permissions
if (authentication.getAuthorities().stream()
.anyMatch(auth -> auth.getAuthority().equals("ROLE_USER"))) {
return "Data saved successfully by user: " + authentication.getName();
}
throw new SecurityException("Insufficient permissions");
}
}
JWT Token Validation
package com.example.service;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.stereotype.Service;
@Service
public class TokenValidationService {
private final JwtDecoder jwtDecoder;
public TokenValidationService(JwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
}
public boolean validateToken(String token) {
try {
Jwt decodedJwt = jwtDecoder.decode(token);
return !decodedJwt.getExpiresAt().isBefore(java.time.Instant.now());
} catch (Exception e) {
return false;
}
}
public String getUsernameFromToken(String token) {
try {
Jwt decodedJwt = jwtDecoder.decode(token);
return decodedJwt.getSubject();
} catch (Exception e) {
return null;
}
}
}
9. React Integration
React App Setup
# Create React app
npx create-react-app keycloak-react-app
cd keycloak-react-app
# Install Keycloak adapter
npm install keycloak-js
Keycloak Configuration
import Keycloak from 'keycloak-js';
const keycloak = new Keycloak({
url: 'http://localhost:8080',
realm: 'my-realm',
clientId: 'my-web-app'
});
export default keycloak;
React App Integration
import React, { useState, useEffect } from 'react';
import keycloak from './keycloak';
function App() {
const [authenticated, setAuthenticated] = useState(false);
const [user, setUser] = useState(null);
useEffect(() => {
keycloak.init({ onLoad: 'check-sso' }).then((authenticated) => {
setAuthenticated(authenticated);
if (authenticated) {
setUser(keycloak.tokenParsed);
}
}).catch(console.error);
}, []);
const login = () => {
keycloak.login();
};
const logout = () => {
keycloak.logout();
};
if (!authenticated) {
return (
<div className="App">
<h1>Welcome to My App</h1>
<button onClick={login}>Login</button>
</div>
);
}
return (
<div className="App">
<h1>Hello {user.preferred_username}!</h1>
<p>Email: {user.email}</p>
<p>First Name: {user.given_name}</p>
<p>Last Name: {user.family_name}</p>
<button onClick={logout}>Logout</button>
<ProtectedComponent />
</div>
);
}
export default App;
Protected Component with API Calls
import React, { useState, useEffect } from 'react';
import keycloak from './keycloak';
function ProtectedComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch('http://localhost:8081/api/user/profile', {
headers: {
'Authorization': 'Bearer ' + keycloak.token,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const result = await response.text();
setData(result);
} else {
throw new Error('Failed to fetch data');
}
} catch (error) {
console.error('Error:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
// Refresh token before API call
keycloak.updateToken(30).then(refreshed => {
if (refreshed) {
console.log('Token refreshed');
}
}).catch(() => {
console.log('Failed to refresh token');
});
}, []);
return (
<div>
<h2>Protected Data</h2>
<button onClick={fetchData} disabled={loading}>
{loading ? 'Loading...' : 'Fetch Protected Data'}
</button>
{data && <div><pre>{data}</pre></div>}
</div>
);
}
export default ProtectedComponent;
Token Refresh Hook
import { useState, useEffect } from 'react';
import keycloak from './keycloak';
export function useKeycloak() {
const [initialized, setInitialized] = useState(false);
const [authenticated, setAuthenticated] = useState(false);
useEffect(() => {
keycloak.init({ onLoad: 'check-sso' }).then((authenticated) => {
setAuthenticated(authenticated);
setInitialized(true);
}).catch(console.error);
// Set up token refresh interval
const refreshInterval = setInterval(() => {
keycloak.updateToken(70).catch(() => {
console.log('Failed to refresh token');
});
}, 60000); // Refresh every minute
return () => clearInterval(refreshInterval);
}, []);
return { initialized, authenticated, keycloak };
}
10. Best Practices
Security Best Practices
Token Security
Short-lived tokens with refresh rotation
Password Policies
Strong password requirements and MFA
Session Management
Proper session timeouts and cleanup
Production Deployment
graph TD
A[Load Balancer] --> B[Keycloak Node 1]
A --> C[Keycloak Node 2]
A --> D[Keycloak Node N]
B --> E[Shared Database]
C --> E
D --> E
B --> F[Cache Cluster]
C --> F
D --> F
Configuration Best Practices
# Production configuration
# Enable HTTPS
proxy=edge
http-enabled=false
# Database configuration
db=postgres
db-url=jdbc:postgresql://postgres:5432/keycloak
db-username=keycloak
db-password=${KEYCLOAK_DB_PASSWORD}
# Cache configuration
cache=ispn
cache-stack=kubernetes
# Logging
log-level=INFO,org.keycloak.events=DEBUG
# Security
ssl-required=external
brute-force-protected=true
failure-factor=30
# Performance
transaction-xa-enabled=false
spi-user-cache-infinispan-max-lifespan=900000
Monitoring and Logging
{
"logging": {
"pattern": "%d{yyyy-MM-dd HH:mm:ss} %-5p [%c] (%t) %s%e%n",
"console": {
"enabled": true,
"level": "INFO"
},
"file": {
"enabled": true,
"level": "DEBUG",
"path": "/var/log/keycloak/keycloak.log"
}
},
"metrics": {
"enabled": true,
"micrometer": {
"enabled": true
}
},
"events": {
"enabled": true,
"admin": {
"enabled": true
},
"login": {
"enabled": true
}
}
}
Essential Best Practices:
- Token Lifespan: Short access tokens (5-30 min), longer refresh tokens
- Password Security: Strong policies, hashing algorithms, breach detection
- MFA Implementation: Multi-factor authentication for sensitive operations
- Regular Audits: Monitor logs, events, and security incidents
- Backup Strategy: Regular backups of realms and configurations
- Update Management: Keep Keycloak updated with security patches