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 │ │ │ • ... │ │ │
│ └──────────────┘ │ └────────────────┘ │ │
│ └──────────────────────┘ │
└─────────────────────────────────────────────────┘
| Element | Description | Example |
|---|---|---|
| Profile | Set of Executors — defines "enforce what" | fapi-2-security-profile |
| Condition | Condition determines which clients are affected — "enforce for whom" | Clients with role fapi-client |
| Executor | Specific enforcement logic — "how to enforce" | Mandatory PKCE S256 |
1.3 Create Client Profile
Go to Realm Settings → Client Policies → tab Profiles
Click Create client profile
Enter Name and Description
Click Save → open profile → click Add executor
1.4 Available Executors
| Executor | Description | Parameter |
|---|---|---|
| Secure Client Authenticator | Requires specific authentication method | Allowed authenticators: client-secret, client-jwt, client-x509 |
| PKCE Enforcer | Required PKCE | Augment: ON (added automatically if client lacks) |
| Secure Signing Algorithm | Only allow secure algorithms | Default: RS256, ES256, PS256 |
| Secure Signing Algorithm for Signed JWT | Algorithm for client JWT auth | PS256, ES256 (RS256 not allowed) |
| Holder-of-Key Enforcer | Required token binding (mTLS or DPoP) | Auto-configure: ON |
| DPoP Proof Verifier | Required DPoP proof in token requests | |
| Confidential Client Enforcer | Only allow confidential clients | |
| Consent Required | Consent screen required | |
| Full Scope Disabled | Disable full scope mapping | |
| Reject Implicit Grant | Do not allow implicit flow | |
| Reject Resource Owner Password Credentials Grant | Disallow ROPC | |
| Secure Redirect URIs Enforcer | Validate redirect URIs | Require HTTPS, no wildcard |
| Secure Request Object | Required JAR (JWT-Secured Authorization Request) | |
| Secure Response Type | Only secure response types allowed | Allowed: code (no token, id_token) |
| Secure Session Enforcer | Enforce session settings |
1.5 Conditions available
| Condition | Description | Example |
|---|---|---|
| Any Client | Applies to all clients | Global security policy |
| Client Access Type | Based on client type (public/confidential) | Enforce PKCE for all public clients |
| Client Roles | Clients with specific roles | Clients with roles fapi-compliant |
| Client Scopes | Client uses specific scope | Clients request scope payment |
| Client Update Source Groups | Based on source create/update client | Clients created via Dynamic Registration |
| Client Update Context | Context when client is updated | Authorization request, Token request |
1.6 Create Client Policy
Go to Realm Settings → Client Policies → tab Policies
Click Create client policy
Enter Name and Description
Add Conditions (identify which clients are affected)
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:
| Request | Description |
|---|---|
| Authorization Code Flow only | Implicit not allowed, ROPC |
| PKCE (S256) | Required for all clients |
| Confidential Client | Required client authentication |
| Secure Signing Algorithms | PS256, ES256 (no RS256) |
| Sender-constrained tokens | DPoP or mTLS token binding |
| Redirect URI exact match | No wildcard |
| HTTPS required | For 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
Go to Realm Settings → Client Policies → tab Profiles
Keycloak provides Global Profiles:
fapi-2-security-profilefapi-2-message-signing-profile
Create Policy using corresponding profile
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-serviceService 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
Open client → tab Service account roles
Click Assign role
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:
Client → tab Credentials → Client Authenticator:
Signed JWTUpload 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:
Client → tab Credentials → Client Authenticator:
X.509 CertificateEnter Subject DN or pattern for certificate matching
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:
Open target client (the client you want to exchange tokens to) → tab Permissions
Enable Permissions Enabled
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
| Mode | Description | Token claims |
|---|---|---|
| Delegation | Service B knows that Service A is acting on behalf of user | act.sub = Service A, sub = user |
| Impersonation | Service B unknown — identical token to direct user request | sub = 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 tokenPOST /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
Realm Settings → Keys → add external issuer's signing key
Or configure Identity Provider for external issuer
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: ONAccess 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
Create Client Profile
baseline-securitywith executors: Reject Implicit Grant, PKCE Enforcer, Secure Signing AlgorithmCreate Client Policy
enforce-baselinewith conditionAny ClientTest: Create new client and try to request token without PKCE → rejected
Test: Try turning on Implicit flow → rejected
Lab 2: FAPI 2.0 Compliance
Create Client Profile using built-in FAPI 2.0 Security Profile
Create a Policy that only applies to clients with the role
fapi-clientCreate confidential client with Signed JWT authentication
Test full authorization flow with PAR + PKCE + DPoP
Lab 3: Client Secret Rotation
Configure Secret Rotation executor (expiration: 60 seconds, grace: 30 seconds for testing)
Create confidential client → record secret A
Wait 60 seconds → regenerate secret → record secret B
Verify: Secret A remains active during the grace period (30 seconds)
Verify: After grace period, only secret B is active
Lab 4: Service Account + Token Exchange
Create 3 clients:
frontend-app(public),api-gateway(confidential + service account),payment-service(confidential)User logs in via
frontend-app→ receives access tokenapi-gatewayreceives tokens from frontend, exchanges new tokens forpayment-serviceVerify: New token has
aud: payment-serviceandact.sub: api-gateway
Lab 5: MCP Server Configuration
Create realm
mcp-demoCreate clients:
mcp-host(confidential),mcp-tools-server(confidential)Create client scopes:
mcp:tools:read,mcp:tools:executeConfigure Audience Mapper for
mcp-host→ audience =mcp-tools-serverGet tokens with Client Credentials flow
Verify token contents: audience, scopes, permissions
Simulate MCP Server validate token using JWKS endpoint
Lab 6: Signed JWT Client Authentication
Generate RSA key pair (
openssl)Create confidential client with authenticator =
Signed JWTUpload certificate to Keycloak
Write script to create and sign client_assertion JWT
Request token with
client_assertioninstead ofclient_secretVerify token received