Skip to content
Published on

The Definitive Guide to Next-Generation Digital Identity, SSO, and Keycloak in Practice

Authors
  • Name
    Twitter

1. Password Fatigue and the Emergence of SSO

As the number of applications used in modern business environments grows exponentially, Password Fatigue has become a serious security threat and a cause of productivity decline. SSO (Single Sign-On) technology emerged to solve this problem, allowing access to all services within a trust relationship with just a single authentication.

The core values gained through SSO are as follows:

  • Centralized MFA: Manage multi-factor authentication in one place for consistent security policy enforcement
  • Immediate Access Revocation: Block access at once from the IdP when employees leave or roles change
  • Zero Trust Foundation: The basis for the "Never Trust, Always Verify" principle

2. SSO Implementation Architecture

SSO systems are divided into three models depending on the environment.

ModelMethodSuitable Environment
Agent-basedInstall agents on individual serversLegacy environments
AgentlessControl traffic via API Gateway / Reverse ProxyCloud native
Identity FederationShare identity across domainsB2B collaboration, multi-org

3. Technical Standard Comparison: SAML vs OAuth 2.0 vs OIDC

CategorySAML 2.0OAuth 2.0OIDC
PurposeAuthenticationAuthorizationAuthentication + Authorization
Data FormatXML (Assertion)JSONJSON (JWT)
TokenSAML AssertionAccess TokenID Token + Access Token
Primary UseEnterprise web appsAPI authorization delegationMobile, SPA, MSA
ComplexityHighMediumLow

OIDC (OpenID Connect) adds an identity layer on top of OAuth 2.0 to handle authentication lightly in JWT format, and is the de facto standard for modern microservice environments.


4. Keycloak Core Architecture

Keycloak is an open-source IAM solution that supports SAML, OAuth 2.0, and OIDC standards. It is backed by Red Hat and is a CNCF incubating project.

4.1 Core Hierarchy

Keycloak Instance
  └── Realm (logical isolation unit, tenant)
        ├── Clients (applications requesting authentication)
        ├── Users
        ├── Groups
        ├── Roles (Realm Roles + Client Roles)
        ├── Identity Providers (external IdP integration)
        ├── Authentication Flows (authentication flow definitions)
        └── Authorization Services (fine-grained authorization policies)
  • Realm: An isolated tenant space. The master Realm is for administration; create separate Realms for actual services
  • Client: OAuth/OIDC client applications (Confidential / Public / Bearer-only)
  • Roles and Groups: The core of permission management. Realm Roles have global scope, Client Roles have specific client scope

4.2 Technology Stack

ComponentRole
QuarkusHigh-performance Java framework runtime
InfinispanIn-memory distributed cache (session, token sync)
PostgreSQL (etc.)Persistent data storage for users, policies

5. Production Deployment

5.1 Docker Deployment

Development Mode (quick start, embedded H2 DB):

docker run --name keycloak -p 8080:8080 \
  -e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
  -e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me \
  quay.io/keycloak/keycloak:latest \
  start-dev

Production Mode (TLS + PostgreSQL required):

docker run --name keycloak -p 8443:8443 -p 9000:9000 -m 2g \
  -e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
  -e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me \
  quay.io/keycloak/keycloak:latest start \
  --hostname=https://auth.example.com \
  --https-certificate-file=/opt/keycloak/conf/cert.pem \
  --https-certificate-key-file=/opt/keycloak/conf/key.pem \
  --db=postgres \
  --db-url=jdbc:postgresql://db-host:5432/keycloak \
  --db-username=keycloak \
  --db-password=change_me \
  --health-enabled=true \
  --metrics-enabled=true

5.2 Kubernetes Operator Deployment

# Install CRDs and Operator
kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.5.4/kubernetes/keycloaks.k8s.keycloak.org-v1.yml
kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.5.4/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml
kubectl create namespace keycloak
kubectl -n keycloak apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.5.4/kubernetes/kubernetes.yml

Keycloak CR Manifest:

apiVersion: k8s.keycloak.org/v2alpha1
kind: Keycloak
metadata:
  name: production-kc
  namespace: keycloak
spec:
  instances: 3
  db:
    vendor: postgres
    host: postgres-db
    usernameSecret:
      name: keycloak-db-secret
      key: username
    passwordSecret:
      name: keycloak-db-secret
      key: password
  http:
    tlsSecret: keycloak-tls-secret
  hostname:
    hostname: auth.example.com
  proxy:
    headers: xforwarded

5.3 keycloak.conf Production Configuration

# Hostname
hostname=https://auth.example.com
hostname-admin=https://admin.auth.example.com

# Database
db=postgres
db-url=jdbc:postgresql://db:5432/keycloak
db-username=keycloak
db-password=${vault.db-password}
db-pool-max-size=100

# TLS
http-enabled=false
https-port=8443
https-certificate-file=/etc/certs/tls.crt
https-certificate-key-file=/etc/certs/tls.key

# Proxy
proxy-headers=xforwarded

# Cache / Clustering
cache=ispn

# Health & Metrics (port 9000)
health-enabled=true
metrics-enabled=true

# Logging
log=console,file
log-console-output=json

6. Authentication Flows

6.1 OIDC Endpoints

All endpoints can be auto-discovered from the Well-Known URL.

GET /realms/{realm}/.well-known/openid-configuration
EndpointPath
Authorization/realms/{realm}/protocol/openid-connect/auth
Token/realms/{realm}/protocol/openid-connect/token
Userinfo/realms/{realm}/protocol/openid-connect/userinfo
Logout/realms/{realm}/protocol/openid-connect/logout
JWKS/realms/{realm}/protocol/openid-connect/certs
Introspect/realms/{realm}/protocol/openid-connect/token/introspect

6.2 Authorization Code Flow + PKCE

This is the recommended standard flow for web applications and SPA/mobile.

Step 1 - Authorization Request (Browser Redirect):

GET /realms/myrealm/protocol/openid-connect/auth?
  response_type=code&
  client_id=my-app&
  redirect_uri=https://myapp.example.com/callback&
  scope=openid profile email&
  state=random-state-value&
  code_challenge=BASE64URL(SHA256(code_verifier))&
  code_challenge_method=S256

Step 2 - Token Exchange (Server-side):

curl -X POST \
  https://auth.example.com/realms/myrealm/protocol/openid-connect/token \
  -d "grant_type=authorization_code" \
  -d "code=AUTHORIZATION_CODE" \
  -d "redirect_uri=https://myapp.example.com/callback" \
  -d "client_id=my-app" \
  -d "client_secret=my-secret" \
  -d "code_verifier=ORIGINAL_CODE_VERIFIER"

PKCE prevents code interception attacks. The code_verifier is a random string generated by the client, and the code_challenge is its SHA256 hash. The original code_verifier is submitted during token exchange for verification.

6.3 Client Credentials Flow

Used for service-to-service (M2M) authentication.

curl -X POST \
  https://auth.example.com/realms/myrealm/protocol/openid-connect/token \
  -d "grant_type=client_credentials" \
  -d "client_id=my-service" \
  -d "client_secret=my-service-secret" \
  -d "scope=openid"

Confidential clients have a built-in Service Account. Assign roles at Clients, then my-service, then Service Account Roles.

6.4 Client Type Selection Guide

TypeSecretUse Case
ConfidentialYesServer-side web apps, backend services
PublicNoSPA, mobile apps (PKCE required)
Bearer-onlyVerify onlyREST API services (Resource Server)

7. Authorization System

7.1 RBAC (Role-Based Access Control)

Role-based access control assigns roles to users and permits or denies access based on those roles.

  • Realm Role: Entire Realm scope (e.g., admin, user)
  • Client Role: Specific client scope (e.g., my-app:editor)
  • Composite Role: A higher-level role that bundles multiple roles

7.2 ABAC (Attribute-Based Access Control)

Attribute-based access control dynamically makes decisions by combining user IP, access time, department information, etc.

Keycloak Authorization Services supports various policy types.

Policy TypeDescription
Role-basedEvaluate Realm/Client roles
Group-basedEvaluate group membership
Time-basedTime period restrictions
Client-basedRequesting client restrictions
JavaScriptCustom JS conditions
RegexPattern matching
AggregatedCombine multiple policies

7.3 Permission Model

"Can X (user) perform Y (action) on Z (resource)?"

# Check permissions with RPT (Requesting Party Token) request
curl -X POST \
  https://auth.example.com/realms/myrealm/protocol/openid-connect/token \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
  -d "audience=my-resource-server"

Decision strategies:

  • Affirmative: Access is granted if at least one policy permits
  • Unanimous: Access is granted only if all policies permit

8. Identity Brokering and Social Login

Keycloak natively supports integration with external IdPs.

8.1 Supported IdPs

TypeSupported List
Social LoginGoogle, GitHub, Facebook, Microsoft, LinkedIn, GitLab, etc.
Protocol-basedOIDC v1.0, OAuth 2.0, SAML 2.0

8.2 Google IdP Configuration Example

  1. Create OAuth 2.0 credentials in Google Cloud Console
  2. Redirect URI: https://auth.example.com/realms/{realm}/broker/google/endpoint
  3. Keycloak Admin Console then Identity Providers then add Google
  4. Enter Client ID and Client Secret

8.3 LDAP/Active Directory Integration

Integration with LDAP is available through User Federation.

SettingActive DirectoryGeneral LDAP
Connection URLldaps://ad-server:636ldap://ldap-server:389
Users DNOU=Users,DC=corp,DC=example,DC=comou=People,dc=example,dc=com
Username AttributesAMAccountNameuid
UUID AttributeobjectGUIDentryUUID

Synchronization modes:

  • READ_ONLY: Read-only from LDAP (safest)
  • WRITABLE: Can modify LDAP attributes from Keycloak
  • UNSYNCED: Store changes only locally in Keycloak

9. Application Integration in Practice

9.1 Spring Boot Resource Server

application.yml:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://auth.example.com/realms/myrealm
          jwk-set-uri: https://auth.example.com/realms/myrealm/protocol/openid-connect/certs

SecurityConfig.java:

@Configuration
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .jwtAuthenticationConverter(jwtAuthenticationConverter())
                )
            );
        return http.build();
    }

    private JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
        converter.setAuthoritiesClaimName("realm_access.roles");
        converter.setAuthorityPrefix("ROLE_");
        JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
        jwtConverter.setJwtGrantedAuthoritiesConverter(converter);
        return jwtConverter;
    }
}

9.2 Next.js (Auth.js / NextAuth.js)

.env.local:

AUTH_KEYCLOAK_ID=my-nextjs-app
AUTH_KEYCLOAK_SECRET=my-client-secret
AUTH_KEYCLOAK_ISSUER=https://auth.example.com/realms/myrealm

auth.ts (Auth.js v5):

import NextAuth from 'next-auth'
import Keycloak from 'next-auth/providers/keycloak'

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [Keycloak],
})

Register the Redirect URI in the Keycloak Client settings:

https://myapp.example.com/api/auth/callback/keycloak

9.3 React SPA (keycloak-js)

import Keycloak from 'keycloak-js'

const keycloak = new Keycloak({
  url: 'https://auth.example.com',
  realm: 'myrealm',
  clientId: 'my-spa',
})

// Initialize with Authorization Code Flow + PKCE
const authenticated = await keycloak.init({
  onLoad: 'login-required',
  pkceMethod: 'S256',
  checkLoginIframe: false,
})

// Use token for API calls
const response = await fetch('/api/data', {
  headers: {
    Authorization: `Bearer ${keycloak.token}`,
  },
})

// Auto-refresh before token expiry
keycloak.onTokenExpired = () => {
  keycloak.updateToken(30).catch(() => keycloak.login())
}

// Check roles
keycloak.hasRealmRole('admin')
keycloak.hasResourceRole('editor', 'my-resource-server')

9.4 Node.js Express

const express = require('express')
const session = require('express-session')
const Keycloak = require('keycloak-connect')

const app = express()
const memoryStore = new session.MemoryStore()

app.use(
  session({
    secret: 'my-secret',
    resave: false,
    saveUninitialized: true,
    store: memoryStore,
  })
)

const keycloak = new Keycloak(
  { store: memoryStore },
  {
    realm: 'myrealm',
    'auth-server-url': 'https://auth.example.com/',
    resource: 'my-node-app',
  }
)

app.use(keycloak.middleware())

// Authentication required endpoint
app.get('/api/protected', keycloak.protect(), (req, res) => {
  res.json({ message: 'Authenticated!' })
})

// Specific role required
app.get('/api/admin', keycloak.protect('realm:admin'), (req, res) => {
  res.json({ message: 'Admin access granted' })
})

app.listen(3000)

10. Token Relay Pattern in MSA Environments

In a microservice architecture, the API Gateway integrates with Keycloak to obtain a JWT token, then relays requests to internal services.

[Client][API Gateway][Keycloak] (obtain/verify token)
         Authorization: Bearer <JWT>
    [Service A][Service B][Service C]

Each service does not need to communicate directly with the IdP; it only verifies the JWT signature locally, achieving excellent performance and security. Simply fetch and cache the public key from Keycloak's JWKS endpoint (/protocol/openid-connect/certs).


11. Admin REST API Usage

11.1 Token Acquisition

# Password method
ACCESS_TOKEN=$(curl -s -X POST \
  "https://auth.example.com/realms/master/protocol/openid-connect/token" \
  -d "grant_type=password" \
  -d "client_id=admin-cli" \
  -d "username=admin" \
  -d "password=admin-password" | jq -r '.access_token')

# Service Account method (recommended for automation)
ACCESS_TOKEN=$(curl -s -X POST \
  "https://auth.example.com/realms/master/protocol/openid-connect/token" \
  -d "grant_type=client_credentials" \
  -d "client_id=my-admin-client" \
  -d "client_secret=my-admin-secret" | jq -r '.access_token')

11.2 User Management

# Create user
curl -X POST \
  "https://auth.example.com/admin/realms/myrealm/users" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "newuser",
    "email": "newuser@example.com",
    "enabled": true,
    "credentials": [{
      "type": "password",
      "value": "temp-password",
      "temporary": true
    }]
  }'

# Search users
curl -X GET \
  "https://auth.example.com/admin/realms/myrealm/users?username=johndoe" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Assign roles
curl -X POST \
  "https://auth.example.com/admin/realms/myrealm/users/{user-id}/role-mappings/realm" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{"id": "ROLE_ID", "name": "admin"}]'

12. High Availability (HA) Architecture

12.1 Distributed Architecture

                    ┌─────────────┐
Load Balancer│
                    └──────┬──────┘
              ┌────────────┼────────────┐
              ▼            ▼            ▼
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │Keycloak 1│ │Keycloak 2│ │Keycloak 3        └────┬─────┘ └────┬─────┘ └────┬─────┘
Infinispan    (session sync)             └──────────────┴───────────────┘
                  ┌──────┴──────┐
PostgreSQL                    (shared DB)                  └─────────────┘
  • Static data (users, policies): stored in the shared DB (PostgreSQL)
  • Dynamic data (sessions, tokens): synchronized across nodes via Infinispan distributed cache

12.2 Kubernetes Health Probe Configuration

spec:
  containers:
    - name: keycloak
      livenessProbe:
        httpGet:
          path: /health/live
          port: 9000
        initialDelaySeconds: 30
        periodSeconds: 10
      readinessProbe:
        httpGet:
          path: /health/ready
          port: 9000
        initialDelaySeconds: 30
        periodSeconds: 10
      startupProbe:
        httpGet:
          path: /health/started
          port: 9000
        failureThreshold: 30
        periodSeconds: 5

12.3 Cross-DC Replication (External Infinispan)

For cases requiring synchronization across geographically separated data centers:

cache=ispn
cache-remote-host=infinispan.example.com
cache-remote-port=11222
cache-remote-username=keycloak
cache-remote-password=change_me
cache-remote-tls-enabled=true

13. Monitoring and Observability

13.1 Prometheus Metrics

# prometheus.yml
scrape_configs:
  - job_name: 'keycloak'
    metrics_path: '/metrics'
    scheme: https
    static_configs:
      - targets: ['keycloak-host:9000']

Enabling event metrics:

bin/kc.sh start \
  --metrics-enabled=true \
  --event-metrics-user-enabled=true \
  --event-metrics-user-tags=realm,client,idp \
  --event-metrics-user-events=LOGIN,LOGIN_ERROR,LOGOUT,TOKEN_REFRESH

13.2 Grafana Dashboards

Keycloak provides official Grafana dashboards.

DashboardPurpose
keycloak-troubleshooting-dashboard.jsonSLI and diagnostic analysis
keycloak-capacity-planning-dashboard.jsonLoad estimation, login flow analysis

Download: github.com/keycloak/keycloak-grafana-dashboard


14. Conclusion

Here is a summary of key points for successful SSO implementation.

  1. Protocol: Default to OIDC; apply SAML only for legacy enterprise apps
  2. Authorization: Start with RBAC as the base structure; combine with ABAC when fine-grained control is needed
  3. High Availability: At least 3 nodes + Infinispan distributed cache + shared DB configuration
  4. Application Integration: Spring Boot uses Resource Server JWT verification, SPA uses keycloak-js + PKCE, Next.js uses Auth.js
  5. Monitoring: Track login failure rates, token issuance volumes, etc., with Prometheus metrics + Grafana dashboards

Keycloak is the cornerstone of next-generation digital identity governance, providing all of this as open source.


References