Chuyển đến nội dung chính

Lesson 9: Client Policies and Advanced Client Configuration

Client Policies architecture (Profiles, Conditions, Executors), FAPI 2.0 Security Profile, Client Secret Rotation, Service Accounts, Audience Support, Confidential Client Credentials (Client ID/Secret, Signed JWT, X.509), Standard Token Exchange, JWT Authorization Grant (RFC 7523), and configuration for MCP Servers.

🔒 DevSecOps — Lesson 9 Lesson 9: Client Policies and Advanced Client Configuration

Keycloak from Basic to Advanced

Part 2: SSO Protocols - OpenID Connect and SAML

xdev.asia

1. Client Policies

Client Policies is a framework that allows enforce security requirements on clients automatically. Instead of having to manually check each client's configuration, you define policies so that Keycloak automatically validates and enforces.

1.1 Why do we need Client Policies?

  • Consistency: Ensures all clients comply with the same security standards

  • Automation: Automatically reject non-compliant requests

  • Compliance: Enforce industry standards (FAPI, PSD2, Open Banking)

  • Governance: Control client registration and configuration

1.2 Architecture: Profiles, Conditions, Executors

Client Policies includes 3 main components:

┌─────────────────────────────────────────────────┐
│                  Client Policy                   │
│                                                   │
│  ┌──────────────┐     ┌──────────────────────┐   │
│  │  Conditions   │     │      Profiles        │   │
│  │ (Khi nào?)    │────>│   (Áp dụng gì?)      │   │
│  │               │     │                      │   │
│  │ • Client Role │     │  ┌────────────────┐  │   │
│  │ • Client Scope│     │  │   Executors    │  │   │
│  │ • Any Client  │     │  │ (Làm gì?)      │  │   │
│  │ • Client      │     │  │                │  │   │
│  │   Access Type │     │  │ • PKCE Enforcer│  │   │
│  │ • Client      │     │  │ • Secure Alg   │  │   │
│  │   Update      │     │  │ • DPoP Verify  │  │   │
│  │   Source      │     │  │ • ...          │  │   │
│  └──────────────┘     │  └────────────────┘  │   │
│                        └──────────────────────┘   │
└─────────────────────────────────────────────────┘
ElementDescriptionExample
ProfileSet of Executors — defines "enforce what"fapi-2-security-profile
ConditionCondition determines which clients are affected — "enforce for whom"Clients with role fapi-client
ExecutorSpecific enforcement logic — "how to enforce"Mandatory PKCE S256

1.3 Create Client Profile

  1. Go to Realm Settings → Client Policies → tab Profiles

  2. Click Create client profile

  3. Enter Name and Description

  4. Click Save → open profile → click Add executor

1.4 Available Executors

ExecutorDescriptionParameter
Secure Client AuthenticatorRequires specific authentication methodAllowed authenticators: client-secret, client-jwt, client-x509
PKCE EnforcerRequired PKCEAugment: ON (added automatically if client lacks)
Secure Signing AlgorithmOnly allow secure algorithmsDefault: RS256, ES256, PS256
Secure Signing Algorithm for Signed JWTAlgorithm for client JWT authPS256, ES256 (RS256 not allowed)
Holder-of-Key EnforcerRequired token binding (mTLS or DPoP)Auto-configure: ON
DPoP Proof VerifierRequired DPoP proof in token requests
Confidential Client EnforcerOnly allow confidential clients
Consent RequiredConsent screen required
Full Scope DisabledDisable full scope mapping
Reject Implicit GrantDo not allow implicit flow
Reject Resource Owner Password Credentials GrantDisallow ROPC
Secure Redirect URIs EnforcerValidate redirect URIsRequire HTTPS, no wildcard
Secure Request ObjectRequired JAR (JWT-Secured Authorization Request)
Secure Response TypeOnly secure response types allowedAllowed: code (no token, id_token)
Secure Session EnforcerEnforce session settings

1.5 Conditions available

ConditionDescriptionExample
Any ClientApplies to all clientsGlobal security policy
Client Access TypeBased on client type (public/confidential)Enforce PKCE for all public clients
Client RolesClients with specific rolesClients with roles fapi-compliant
Client ScopesClient uses specific scopeClients request scope payment
Client Update Source GroupsBased on source create/update clientClients created via Dynamic Registration
Client Update ContextContext when client is updatedAuthorization request, Token request

1.6 Create Client Policy

  1. Go to Realm Settings → Client Policies → tab Policies

  2. Click Create client policy

  3. Enter Name and Description

  4. Add Conditions (identify which clients are affected)

  5. Add Client Profiles (which profile applies)

# Ví dụ: Tạo policy enforce PKCE cho tất cả public clients
Profile: pkce-required-profile
  Executors:
    - PKCE Enforcer
        Augment: ON (auto-add PKCE nếu client không gửi)

Policy: enforce-pkce-for-public
  Conditions:
    - Client Access Type: public
  Profiles:
    - pkce-required-profile

1.7 Practical Policy Example

Policy 1: Baseline Security for all clients

Profile: baseline-security
  Executors:
    - Reject Implicit Grant
    - Reject Resource Owner Password Credentials Grant
    - PKCE Enforcer (S256)
    - Secure Signing Algorithm (RS256, ES256, PS256)

Policy: baseline-all-clients Conditions: - Any Client Profiles: - baseline-security

Policy 2: High-Security cho Financial APIs

Profile: financial-api-profile
  Executors:
    - Confidential Client Enforcer
    - Holder-of-Key Enforcer (mTLS hoặc DPoP)
    - Secure Client Authenticator (private_key_jwt, client-x509)
    - Secure Request Object Required
    - Consent Required
    - Secure Redirect URIs Enforcer (HTTPS only)

Policy: financial-api-policy Conditions: - Client Scopes: fapi-scope Profiles: - financial-api-profile

2. FAPI 2.0 Security Profile

FAPI (Financial-grade API) is a set of high security standards developed by the OpenID Foundation, widely used in Open Banking, Payment Services Directive 2 (PSD2), and financial applications.

2.1 FAPI 2.0 Baseline Profile

Keycloak provides built-in profiles for FAPI 2.0:

RequestDescription
Authorization Code Flow onlyImplicit not allowed, ROPC
PKCE (S256)Required for all clients
Confidential ClientRequired client authentication
Secure Signing AlgorithmsPS256, ES256 (no RS256)
Sender-constrained tokensDPoP or mTLS token binding
Redirect URI exact matchNo wildcard
HTTPS requiredFor all endpoints

2.2 FAPI 2.0 Advanced Profile (Message Signing)

In addition to baseline, Advanced Profile adds:

  • PAR (Pushed Authorization Requests) — RFC 9126: send authorization request via backchannel before redirect

  • JAR (JWT-Secured Authorization Request) — RFC 9101: authorization parameters signed in JWT

  • JARM (JWT-Secured Authorization Response Mode): authorization response signed in JWT

# PAR request — gửi authorization params qua backchannel
POST /realms/my-realm/protocol/openid-connect/ext/par/request
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)

response_type=code&
client_id=my-fapi-client&
redirect_uri=https://myapp.com/callback&
scope=openid payments&
state=random-state&
code_challenge=code_challenge_value&
code_challenge_method=S256

# Response
{
  "request_uri": "urn:ietf:params:oauth:request_uri:abc123",
  "expires_in": 60
}

# Authorization request chỉ chứa request_uri
GET /realms/my-realm/protocol/openid-connect/auth?
  client_id=my-fapi-client&
  request_uri=urn:ietf:params:oauth:request_uri:abc123

2.3 Enable FAPI 2.0 in Keycloak

  1. Go to Realm Settings → Client Policies → tab Profiles

  2. Keycloak provides Global Profiles:

    • fapi-2-security-profile
    • fapi-2-message-signing-profile
  3. Create Policy using corresponding profile

  4. Assign Condition to select clients needing compliance

# Ví dụ: Enforce FAPI 2.0 cho clients có scope "fapi"
Policy: fapi-2-enforcement
  Conditions:
    - Client Scopes: fapi
  Profiles:
    - fapi-2-security-profile     # Built-in global profile
    - fapi-2-message-signing-profile  # Thêm nếu cần message signing

3. Client Secret Rotation

Client Secret Rotation allows changing the client secret does not cause downtime — the old secret remains active during a transition period.

3.1 Configure Client Secret Rotation

Use Client Policy with executor Secret Rotation:

# Tạo Profile với Secret Rotation executor
Profile: secret-rotation-profile
  Executors:
    - Secret Rotation
        Secret Expiration: 2592000        # 30 ngày (tính bằng giây)
        Rotated Secret Expiration: 604800  # Grace period: 7 ngày
        Remain Expiration: 604800          # Thời gian cảnh báo trước khi hết hạn

How it works:

Timeline:
┌──────────────────────────────────────────────────────────┐
│ Ngày 0         Ngày 23        Ngày 30          Ngày 37  │
│   │               │              │                │     │
│   ▼               ▼              ▼                ▼     │
│ Secret A      Cảnh báo      Secret B           Secret A │
│ created       sắp hết hạn   created + active   hết hạn  │
│                              Secret A vẫn       hoàn toàn│
│                              hoạt động                   │
│                              (grace period)              │
└──────────────────────────────────────────────────────────┘

Khoảng grace period (Ngày 30-37):

  • Secret B: active (primary)
  • Secret A: vẫn valid (rotated secret, grace) → Ứng dụng có 7 ngày để chuyển sang Secret B

3.2 Deploy Secret Rotation

# 1. Lấy current secret
CURRENT_SECRET=$(curl -s -X GET \
  "$KC_URL/admin/realms/my-realm/clients/$CLIENT_UUID/client-secret" \
  -H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.value')

2. Rotate secret — regenerate new secret

curl -s -X POST
"$KC_URL/admin/realms/my-realm/clients/$CLIENT_UUID/client-secret"
-H "Authorization: Bearer $ADMIN_TOKEN"

3. Lấy new secret

NEW_SECRET=$(curl -s -X GET
"$KC_URL/admin/realms/my-realm/clients/$CLIENT_UUID/client-secret"
-H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.value')

4. Update ứng dụng với new secret

Trong grace period, cả current và new secret đều hoạt động

4. Service Accounts

When Service accounts roles is enabled for a confidential client, Keycloak creates a special service account user for that client. This user represents the client in machine-to-machine operations.

4.1 Service Account User

# Service account user naming convention
Username: service-account-{client-id}
# Ví dụ: service-account-my-backend-service

Service account user có các đặc điểm:

- Không có password (authenticate bằng client credentials)

- Có thể gán realm roles và client roles

- Có thể thêm user attributes

- Xuất hiện trong Users list (với filter service accounts)

4.2 Assign Roles to Service Account

  1. Open client → tab Service account roles

  2. Click Assign role

  3. Select realm roles or filter by clients to assign client roles

# Admin CLI: Gán roles
# Gán realm role
bin/kcadm.sh add-roles -r my-realm \
  --uusername service-account-my-backend-service \
  --rolename realm-admin

# Gán client role từ client khác
bin/kcadm.sh add-roles -r my-realm \
  --uusername service-account-my-backend-service \
  --cclientid realm-management \
  --rolename manage-users

# REST API: Gán role
# Lấy service account user
SA_USER=$(curl -s -X GET \
  "$KC_URL/admin/realms/my-realm/clients/$CLIENT_UUID/service-account-user" \
  -H "Authorization: Bearer $ADMIN_TOKEN")

SA_USER_ID=$(echo $SA_USER | jq -r '.id')

# Gán realm role
ROLE_ID=$(curl -s -X GET \
  "$KC_URL/admin/realms/my-realm/roles/admin" \
  -H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.id')

curl -s -X POST \
  "$KC_URL/admin/realms/my-realm/users/$SA_USER_ID/role-mappings/realm" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{"id":"'$ROLE_ID'","name":"admin"}]'

4.3 Best Practices cho Service Accounts

  • Least privilege: Only assign necessary roles to each service

  • Separate clients: Create separate clients for each microservice, do not share them

  • Audit: Enable events logging to track service account activities

  • Short token lifespan: Access token for service accounts should be short (1-5 minutes)

  • Rotate credentials: Use Client Secret Rotation or certificate-based auth

5. Audience Support

Audience (aud claim) determines which resource server access token is intended to be used. This is an important security mechanism to prevent tokens from being used in unwanted services.

5.1 Problem

# Mặc định, access token chỉ có aud = client-id đã request
{
  "aud": "my-frontend-app",     // ← chỉ có client đã request
  "azp": "my-frontend-app"
}

Resource Server (my-api-service) verify token:

→ aud không chứa "my-api-service"

→ REJECT! (nếu resource server validate audience)

5.2 Solution: Audience Protocol Mapper

Add Audience Mapper to the client or client scope to add the resource server to aud:

# Cách 1: Thêm Audience Mapper trực tiếp vào client
Client: my-frontend-app → Client scopes → Dedicated scope → Add mapper
  Mapper Type: Audience
  Name: api-audience
  Included Client Audience: my-api-service
  Included Custom Audience: (trống)
  Add to ID token: OFF
  Add to access token: ON

# Cách 2: Tạo Client Scope chứa Audience Mapper
Client Scope: api-access
  Mapper: Audience → my-api-service
  Gán scope cho frontend client

# Kết quả trong access token:
{
  "aud": ["my-frontend-app", "my-api-service"],
  "azp": "my-frontend-app"
}

5.3 Audience Resolve Mapper

Keycloak has built-in Audience Resolve mapper (in default scope roles) — automatically adds aud for clients that have client roles:

# Nếu user có role "app-admin" của client "my-api-service"
# → Audience Resolve tự động thêm "my-api-service" vào aud
{
  "aud": ["my-frontend-app", "my-api-service"],
  "resource_access": {
    "my-api-service": {
      "roles": ["app-admin"]
    }
  }
}

6. Confidential Client Credentials

6.1 Client ID and Secret

Simple method — client sends ID and secret in request:

# Cách 1: Form parameter
POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&
client_id=my-client&
client_secret=my-secret

# Cách 2: HTTP Basic Authentication
POST /token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)

grant_type=client_credentials

6.2 Signed JWT (private_key_jwt)

Client creates and signs JWT with private key, sends to Keycloak. Keycloak verify with registered public key/certificate.

Configuration in Keycloak:

  1. Client → tab Credentials → Client Authenticator: Signed JWT

  2. Upload client certificate or JWKS URL

# Tạo key pair cho client
openssl genrsa -out client-private.pem 2048
openssl req -new -x509 -key client-private.pem -out client-cert.pem -days 365

# Upload client-cert.pem vào Keycloak client Credentials tab

# Token request với client_assertion
POST /realms/my-realm/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&
client_id=my-client&
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&
client_assertion=eyJhbGciOiJSUzI1NiIs...

Client Assertion JWT structure:

{
  "iss": "my-client",                    // Client ID
  "sub": "my-client",                    // Client ID
  "aud": "http://localhost:8080/realms/my-realm",  // Token endpoint
  "iat": 1711800000,
  "exp": 1711800060,                     // Short-lived (60s)
  "jti": "unique-jwt-id"                 // Unique ID
}

6.3 X.509 Certificate / Mutual TLS

Client authenticates with a client TLS certificate (Mutual TLS — mTLS). This is the most secure method.

Configuration:

  1. Client → tab Credentials → Client Authenticator: X.509 Certificate

  2. Enter Subject DN or pattern for certificate matching

  3. Configure Keycloak server enable mTLS endpoint

# Keycloak mTLS configuration (quarkus)
# conf/keycloak.conf hoặc environment variables
KC_HTTPS_CLIENT_AUTH=request
KC_HTTPS_KEY_STORE_FILE=/opt/keycloak/certs/server-keystore.p12
KC_HTTPS_TRUST_STORE_FILE=/opt/keycloak/certs/truststore.p12

# Client gọi token endpoint với client certificate
curl -s -X POST \
  "https://localhost:8443/realms/my-realm/protocol/openid-connect/token" \
  --cert client-cert.pem \
  --key client-private.pem \
  -d "grant_type=client_credentials" \
  -d "client_id=my-mtls-client"

Combining mTLS with certificate-bound tokens:

# Access token chứa certificate thumbprint
{
  "cnf": {
    "x5t#S256": "sha256-thumbprint-of-client-certificate"
  }
}

Resource server verify:

1. Client gửi request với TLS client certificate

2. Resource server extract certificate thumbprint

3. So sánh với cnf.x5t#S256 trong access token

→ Nếu match → token hợp lệ + bound to correct client

7. Standard Token Exchange (RFC 8693)

Token Exchange allows a service exchange token to receive new tokens with different permissions or audiences.

7.1 Use Cases

  • Delegation: Service A wants to call Service B "on behalf of" the user — exchange access token to get a new token with audience = Service B

  • Impersonation: Admin wants to act as another user

  • Token type conversion: Exchange access token for SAML assertion (or vice versa)

7.2 Token Exchange Configuration

Token Exchange in Keycloak is preview feature — needs to be enabled:

# Bật feature
bin/kc.sh start-dev --features=token-exchange

# Docker
docker run -e KC_FEATURES=token-exchange quay.io/keycloak/keycloak:26.2.4 start-dev

Configure permissions:

  1. Open target client (the client you want to exchange tokens to) → tab Permissions

  2. Enable Permissions Enabled

  3. Click token-exchange permission → configure policy to allow source client exchange

# Token Exchange request
POST /realms/my-realm/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token=USER_ACCESS_TOKEN&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
requested_token_type=urn:ietf:params:oauth:token-type:access_token&
audience=target-service&
client_id=source-service&
client_secret=SOURCE_SECRET

# Response — token mới cho target-service
{
  "access_token": "new-token-for-target-service",
  "token_type": "Bearer",
  "expires_in": 300,
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token"
}

7.3 Delegation vs Impersonation

ModeDescriptionToken claims
DelegationService B knows that Service A is acting on behalf of useract.sub = Service A, sub = user
ImpersonationService B unknown — identical token to direct user requestsub = user (without act)

8. JWT Authorization Grant (RFC 7523)

Allows clients to use a JWT assertion issued by trusted issuer to obtain an access token without user interaction.

8.1 Flow

# External issuer (ví dụ: Azure AD, Google) cấp JWT cho client
# Client gửi JWT đến Keycloak để exchange lấy Keycloak access token

POST /realms/my-realm/protocol/openid-connect/token Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer& assertion=eyJhbGciOiJSUzI1NiIs...& # JWT from external issuer client_id=my-client& client_secret=my-secret& scope=openid

8.2 Configure JWT Grant

  1. Realm Settings → Keys → add external issuer's signing key

  2. Or configure Identity Provider for external issuer

  3. Client must have Service accounts enabled

9. Configure Keycloak for MCP Servers

Model Context Protocol (MCP) servers use OAuth 2.0 to authenticate clients. Keycloak can serve as Authorization Server for the MCP ecosystem.

9.1 MCP OAuth 2.0 Flow

MCP specification requires OAuth 2.0 for server-to-server and client-to-server authentication:

┌──────────┐     ┌──────────┐     ┌──────────┐
│ MCP Host │     │ Keycloak │     │MCP Server│
│ (Client) │     │  (AuthZ) │     │(Resource)│
└────┬─────┘     └────┬─────┘     └────┬─────┘
     │                │                │
     │ 1. Request     │                │
     │    auth info    │                │
     │───────────────────────────────>│
     │ 2. Return      │                │
     │    auth metadata│                │
     │<──────────────────────────────│
     │                │                │
     │ 3. Authorization Code Flow     │
     │    (hoặc Client Credentials)   │
     │───────────────>│                │
     │ 4. Tokens      │                │
     │<───────────────│                │
     │                │                │
     │ 5. API call with access token  │
     │───────────────────────────────>│
     │ 6. MCP Server validates token  │
     │    via Keycloak JWKS/Introspect│
     │<──────────────────────────────│

9.2 Create Client for MCP Host

# MCP Host client — ứng dụng AI/LLM kết nối tới MCP servers
Client ID: mcp-host-app
Client type: OpenID Connect
Client authentication: ON (confidential)

Capability Config: Standard flow: ON # Cho interactive MCP sessions Service accounts roles: ON # Cho automated MCP operations

Access Settings: Valid redirect URIs: http://localhost:3001/callback Web origins: http://localhost:3001

Advanced: PKCE Code Challenge Method: S256 Access Token Lifespan: 300 # 5 phút

9.3 Create Client for MCP Server (Resource Server)

# MCP Server client — validate incoming tokens
Client ID: mcp-tool-server
Client type: OpenID Connect
Client authentication: ON (confidential)

Capability Config: Standard flow: OFF Service accounts roles: ON # Nếu MCP server cần gọi Keycloak APIs

MCP Server cấu hình JWT validation

Sử dụng Keycloak JWKS endpoint để verify access tokens

JWKS_URI: http://localhost:8080/realms/my-realm/protocol/openid-connect/certs ISSUER: http://localhost:8080/realms/my-realm

9.4 Create Scopes for MCP Operations

# Tạo Client Scopes cho MCP permissions
Client Scope: mcp:tools:read
  Type: Optional
  Description: Read access to MCP tools
  Protocol Mapper: Hardcoded claim
    Token Claim Name: mcp_permissions
    Claim Value: ["tools:read"]

Client Scope: mcp:tools:execute Type: Optional Description: Execute MCP tools Protocol Mapper: Hardcoded claim Token Claim Name: mcp_permissions Claim Value: ["tools:execute"]

Client Scope: mcp:resources:read Type: Optional Description: Read MCP resources Protocol Mapper: Hardcoded claim Token Claim Name: mcp_permissions Claim Value: ["resources:read"]

Gán scopes cho MCP Host client

Client: mcp-host-app Default scopes: mcp:tools:read, mcp:resources:read Optional scopes: mcp:tools:execute

9.5 Audience Mapper cho MCP

# MCP Host client cần access token với audience = MCP Server
Client: mcp-host-app → Client scopes → Dedicated scope → Add mapper
  Mapper Type: Audience
  Name: mcp-server-audience
  Included Client Audience: mcp-tool-server
  Add to access token: ON

Access token kết quả:

{ "iss": "http://localhost:8080/realms/my-realm", "sub": "user-or-service-account-id", "aud": ["mcp-host-app", "mcp-tool-server"], "azp": "mcp-host-app", "scope": "openid mcp:tools:read mcp:resources:read", "mcp_permissions": ["tools:read", "resources:read"] }

9.6 Token Exchange cho MCP Multi-Server

When MCP Host needs to call many different MCP servers, use Token Exchange to get tokens for each server:

# MCP Host có access token cho mcp-tool-server-1
# Cần access mcp-tool-server-2

POST /realms/my-realm/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token=CURRENT_ACCESS_TOKEN&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
audience=mcp-tool-server-2&
client_id=mcp-host-app&
client_secret=HOST_SECRET&
scope=mcp:tools:execute

9.7 Client Policy cho MCP

# Enforce security cho tất cả MCP clients
Profile: mcp-security-profile
  Executors:
    - PKCE Enforcer (S256)
    - Confidential Client Enforcer
    - Secure Signing Algorithm (RS256, ES256)
    - Reject Implicit Grant
    - Reject Resource Owner Password Credentials Grant
    - Holder-of-Key Enforcer  # DPoP cho high-security MCP operations

Policy: mcp-clients-policy Conditions: - Client Scopes: mcp:tools:read # Áp dụng cho clients request MCP scopes Profiles: - mcp-security-profile

10. Practice exercises

Lab 1: Client Policies — Baseline Security

  1. Create Client Profile baseline-security with executors: Reject Implicit Grant, PKCE Enforcer, Secure Signing Algorithm

  2. Create Client Policy enforce-baseline with condition Any Client

  3. Test: Create new client and try to request token without PKCE → rejected

  4. Test: Try turning on Implicit flow → rejected

Lab 2: FAPI 2.0 Compliance

  1. Create Client Profile using built-in FAPI 2.0 Security Profile

  2. Create a Policy that only applies to clients with the role fapi-client

  3. Create confidential client with Signed JWT authentication

  4. Test full authorization flow with PAR + PKCE + DPoP

Lab 3: Client Secret Rotation

  1. Configure Secret Rotation executor (expiration: 60 seconds, grace: 30 seconds for testing)

  2. Create confidential client → record secret A

  3. Wait 60 seconds → regenerate secret → record secret B

  4. Verify: Secret A remains active during the grace period (30 seconds)

  5. Verify: After grace period, only secret B is active

Lab 4: Service Account + Token Exchange

  1. Create 3 clients: frontend-app (public), api-gateway (confidential + service account), payment-service (confidential)

  2. User logs in via frontend-app → receives access token

  3. api-gateway receives tokens from frontend, exchanges new tokens for payment-service

  4. Verify: New token has aud: payment-service and act.sub: api-gateway

Lab 5: MCP Server Configuration

  1. Create realm mcp-demo

  2. Create clients: mcp-host (confidential), mcp-tools-server (confidential)

  3. Create client scopes: mcp:tools:read, mcp:tools:execute

  4. Configure Audience Mapper for mcp-host → audience = mcp-tools-server

  5. Get tokens with Client Credentials flow

  6. Verify token contents: audience, scopes, permissions

  7. Simulate MCP Server validate token using JWKS endpoint

Lab 6: Signed JWT Client Authentication

  1. Generate RSA key pair (openssl)

  2. Create confidential client with authenticator = Signed JWT

  3. Upload certificate to Keycloak

  4. Write script to create and sign client_assertion JWT

  5. Request token with client_assertion instead of client_secret

  6. Verify token received