first commit
This commit is contained in:
@@ -0,0 +1,320 @@
|
|||||||
|
# OIDC & OAuth2 — curl walkthrough
|
||||||
|
|
||||||
|
Testing the full OIDC/OAuth2 flow against the local Keycloak stack.
|
||||||
|
|
||||||
|
**Prerequisites:** `curl`, `jq`, stack running (`docker compose up -d`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
REALM=demo
|
||||||
|
USER=demo-user
|
||||||
|
PASS=demo
|
||||||
|
REDIRECT=http://localhost:3000/callback
|
||||||
|
|
||||||
|
KC=http://localhost:8080/realms/$REALM/protocol/openid-connect
|
||||||
|
|
||||||
|
# Clients (see demo-realm.yaml for details)
|
||||||
|
CLIENT=demo-app
|
||||||
|
CLIENT_PKCE=demo-app-pkce
|
||||||
|
CLIENT_BACKEND=demo-backend
|
||||||
|
BACKEND_SECRET=demo-backend-secret
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Discovery
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s http://localhost:8080/realms/$REALM/.well-known/openid-configuration | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
Key fields: `authorization_endpoint`, `token_endpoint`, `userinfo_endpoint`, `jwks_uri`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Authorization Code
|
||||||
|
|
||||||
|
Standard flow — the client never sees the user's password. Keycloak handles authentication and issues a short-lived code exchanged for tokens.
|
||||||
|
|
||||||
|
**Step 1 — get the login form, submit credentials**
|
||||||
|
|
||||||
|
Keycloak returns an HTML form with a session-specific `action` URL. Credentials must be posted to that URL, not to the auth endpoint directly.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
STATE=$(openssl rand -hex 16)
|
||||||
|
|
||||||
|
LOGIN_URL=$(curl -s -c /tmp/kc-cookies \
|
||||||
|
"$KC/auth?response_type=code&client_id=$CLIENT&redirect_uri=$REDIRECT&scope=openid+profile+email&state=$STATE" \
|
||||||
|
| grep -oE 'action="[^"]+"' | head -1 | cut -d'"' -f2 | sed 's/&/\&/g')
|
||||||
|
|
||||||
|
LOCATION=$(curl -s -b /tmp/kc-cookies \
|
||||||
|
-X POST "$LOGIN_URL" \
|
||||||
|
--data-urlencode "username=$USER" \
|
||||||
|
--data-urlencode "password=$PASS" \
|
||||||
|
-D - -o /dev/null \
|
||||||
|
| grep -i "^location:" | tr -d '\r' | cut -d' ' -f2)
|
||||||
|
|
||||||
|
CODE=$(echo "$LOCATION" | grep -oE 'code=[^&]+' | cut -d= -f2)
|
||||||
|
echo "Code: $CODE"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2 — exchange the code for tokens**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
RESPONSE=$(curl -s -X POST $KC/token \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "grant_type=authorization_code&client_id=$CLIENT&code=$CODE&redirect_uri=$REDIRECT")
|
||||||
|
|
||||||
|
echo $RESPONSE | jq .
|
||||||
|
ACCESS_TOKEN=$(echo $RESPONSE | jq -r .access_token)
|
||||||
|
REFRESH_TOKEN=$(echo $RESPONSE | jq -r .refresh_token)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Authorization Code + PKCE
|
||||||
|
|
||||||
|
Same flow with PKCE (Proof Key for Code Exchange). Prevents authorization code interception attacks — mandatory for public clients in production. `demo-app-pkce` enforces `S256`.
|
||||||
|
|
||||||
|
**Step 1 — generate verifier and challenge**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# code_verifier: random URL-safe string (43-128 chars)
|
||||||
|
CODE_VERIFIER=$(openssl rand -base64 96 | tr -d '=+/\n' | cut -c1-64)
|
||||||
|
|
||||||
|
# code_challenge: BASE64URL(SHA256(code_verifier))
|
||||||
|
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | base64 | tr '+/' '-_' | tr -d '=')
|
||||||
|
|
||||||
|
echo "Verifier: $CODE_VERIFIER"
|
||||||
|
echo "Challenge: $CODE_CHALLENGE"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2 — get the login form with challenge, submit credentials**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
STATE=$(openssl rand -hex 16)
|
||||||
|
|
||||||
|
LOGIN_URL=$(curl -s -c /tmp/kc-pkce-cookies \
|
||||||
|
"$KC/auth?response_type=code&client_id=$CLIENT_PKCE&redirect_uri=$REDIRECT&scope=openid+profile+email&state=$STATE&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256" \
|
||||||
|
| grep -oE 'action="[^"]+"' | head -1 | cut -d'"' -f2 | sed 's/&/\&/g')
|
||||||
|
|
||||||
|
LOCATION=$(curl -s -b /tmp/kc-pkce-cookies \
|
||||||
|
-X POST "$LOGIN_URL" \
|
||||||
|
--data-urlencode "username=$USER" \
|
||||||
|
--data-urlencode "password=$PASS" \
|
||||||
|
-D - -o /dev/null \
|
||||||
|
| grep -i "^location:" | tr -d '\r' | cut -d' ' -f2)
|
||||||
|
|
||||||
|
CODE=$(echo "$LOCATION" | grep -oE 'code=[^&]+' | cut -d= -f2)
|
||||||
|
echo "Code: $CODE"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3 — exchange the code + verifier for tokens**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
RESPONSE=$(curl -s -X POST $KC/token \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "grant_type=authorization_code&client_id=$CLIENT_PKCE&code=$CODE&redirect_uri=$REDIRECT&code_verifier=$CODE_VERIFIER")
|
||||||
|
|
||||||
|
echo $RESPONSE | jq .
|
||||||
|
ACCESS_TOKEN=$(echo $RESPONSE | jq -r .access_token)
|
||||||
|
REFRESH_TOKEN=$(echo $RESPONSE | jq -r .refresh_token)
|
||||||
|
```
|
||||||
|
|
||||||
|
> To verify PKCE is enforced: try the exchange without `code_verifier` — Keycloak returns `invalid_grant`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Client Credentials
|
||||||
|
|
||||||
|
Machine-to-machine — no user involved. The client authenticates with its own credentials and receives a token tied to its service account.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
RESPONSE=$(curl -s -X POST $KC/token \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-u "$CLIENT_BACKEND:$BACKEND_SECRET" \
|
||||||
|
-d "grant_type=client_credentials")
|
||||||
|
|
||||||
|
echo $RESPONSE | jq .
|
||||||
|
M2M_TOKEN=$(echo $RESPONSE | jq -r .access_token)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Password Grant (ROPC — for debugging only)
|
||||||
|
|
||||||
|
The client sends credentials directly to Keycloak. **Never use in production.** Useful for quick scripted tests only.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
RESPONSE=$(curl -s -X POST $KC/token \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "grant_type=password&client_id=$CLIENT&username=$USER&password=$PASS")
|
||||||
|
|
||||||
|
echo $RESPONSE | jq .
|
||||||
|
ACCESS_TOKEN=$(echo $RESPONSE | jq -r .access_token)
|
||||||
|
REFRESH_TOKEN=$(echo $RESPONSE | jq -r .refresh_token)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Scope
|
||||||
|
|
||||||
|
Scope controls **what the token allows** — the capabilities granted by the user or the authorization server. It is a contract between the client and the resource server.
|
||||||
|
|
||||||
|
The demo realm defines two optional scopes on `demo-app`: `app:read` and `app:write`. Optional scopes are only included when explicitly requested.
|
||||||
|
|
||||||
|
**Request a specific scope**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
RESPONSE=$(curl -s -X POST $KC/token \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "grant_type=password&client_id=$CLIENT&username=$USER&password=$PASS&scope=openid+app:read")
|
||||||
|
|
||||||
|
echo $RESPONSE | jq -r .scope
|
||||||
|
ACCESS_TOKEN=$(echo $RESPONSE | jq -r .access_token)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Inspect scope in the token**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo $ACCESS_TOKEN | cut -d. -f2 \
|
||||||
|
| tr -- '-_' '+/' \
|
||||||
|
| awk '{l=length($0)%4; if(l==2) print $0"=="; else if(l==3) print $0"="; else print $0}' \
|
||||||
|
| base64 -d | jq .scope
|
||||||
|
```
|
||||||
|
|
||||||
|
**Without the optional scope** — `app:read` is absent from the token:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X POST $KC/token \
|
||||||
|
-d "grant_type=password&client_id=$CLIENT&username=$USER&password=$PASS" \
|
||||||
|
| jq -r .scope
|
||||||
|
```
|
||||||
|
|
||||||
|
The resource server checks the `scope` claim before executing an operation. A token without `app:write` must be rejected on write endpoints regardless of who the user is.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Audience
|
||||||
|
|
||||||
|
Audience controls **who can accept the token** — which resource servers are authorized to consume it. It is a contract between the token issuer and the downstream services.
|
||||||
|
|
||||||
|
`demo-app` has an audience mapper configured for `demo-backend`. Every token issued to `demo-app` carries `aud=demo-backend`, regardless of scope.
|
||||||
|
|
||||||
|
**Inspect audience in the token**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
RESPONSE=$(curl -s -X POST $KC/token \
|
||||||
|
-d "grant_type=password&client_id=$CLIENT&username=$USER&password=$PASS")
|
||||||
|
ACCESS_TOKEN=$(echo $RESPONSE | jq -r .access_token)
|
||||||
|
|
||||||
|
echo $ACCESS_TOKEN | cut -d. -f2 \
|
||||||
|
| tr -- '-_' '+/' \
|
||||||
|
| awk '{l=length($0)%4; if(l==2) print $0"=="; else if(l==3) print $0"="; else print $0}' \
|
||||||
|
| base64 -d | jq '{aud, scope}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `"aud": ["demo-backend", "account"]`
|
||||||
|
|
||||||
|
**What the resource server validates**
|
||||||
|
|
||||||
|
When `demo-backend` receives a token, it must verify that its own client ID appears in `aud`. If not, it rejects the request even if the token is otherwise valid (valid signature, not expired, correct scope).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Introspection: demo-backend validates the token and checks aud internally
|
||||||
|
curl -s -X POST $KC/token/introspect \
|
||||||
|
-u "$CLIENT_BACKEND:$BACKEND_SECRET" \
|
||||||
|
-d "token=$ACCESS_TOKEN" | jq '{active, aud, scope}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**PKCE protects token issuance. Audience protects token usage.**
|
||||||
|
A token intercepted after issuance can only be replayed against services listed in `aud` — nowhere else.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Decode the access token
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo $ACCESS_TOKEN | cut -d. -f2 \
|
||||||
|
| tr -- '-_' '+/' \
|
||||||
|
| awk '{l=length($0)%4; if(l==2) print $0"=="; else if(l==3) print $0"="; else print $0}' \
|
||||||
|
| base64 -d | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
Notable claims: `sub`, `preferred_username`, `realm_access.roles`, `exp`, `iat`, `iss`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Userinfo endpoint
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s $KC/userinfo \
|
||||||
|
-H "Authorization: Bearer $ACCESS_TOKEN" | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Token introspection
|
||||||
|
|
||||||
|
> Public clients cannot call this endpoint. Introspection is reserved for confidential clients — called by backend APIs to validate tokens they receive.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X POST $KC/token/introspect \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-u "$CLIENT_BACKEND:$BACKEND_SECRET" \
|
||||||
|
-d "token=$ACCESS_TOKEN" | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
Check `"active": true` in the response.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Refresh the token
|
||||||
|
|
||||||
|
```bash
|
||||||
|
RESPONSE=$(curl -s -X POST $KC/token \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "grant_type=refresh_token&client_id=$CLIENT&refresh_token=$REFRESH_TOKEN")
|
||||||
|
|
||||||
|
echo $RESPONSE | jq .
|
||||||
|
ACCESS_TOKEN=$(echo $RESPONSE | jq -r .access_token)
|
||||||
|
REFRESH_TOKEN=$(echo $RESPONSE | jq -r .refresh_token)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Logout (token revocation)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X POST $KC/logout \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "client_id=$CLIENT&refresh_token=$REFRESH_TOKEN"
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify the token is revoked by attempting a refresh — it should return `invalid_grant`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. JWKS — public keys
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s http://localhost:8080/realms/$REALM/protocol/openid-connect/certs | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Admin API — list users
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ADMIN_TOKEN=$(curl -s -X POST \
|
||||||
|
http://localhost:8080/realms/master/protocol/openid-connect/token \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "grant_type=password&client_id=admin-cli&username=admin&password=$KEYCLOAK_ADMIN_PASSWORD" \
|
||||||
|
| jq -r .access_token)
|
||||||
|
|
||||||
|
curl -s http://localhost:8080/admin/realms/$REALM/users \
|
||||||
|
-H "Authorization: Bearer $ADMIN_TOKEN" | jq .
|
||||||
|
```
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# pm-keycloak
|
||||||
|
|
||||||
|
Keycloak deployment with declarative realm configuration via keycloak-config-cli.
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
- **Keycloak 26.5.4** — identity and access management
|
||||||
|
- **PostgreSQL 16** — persistent storage
|
||||||
|
- **keycloak-config-cli 6.5.0** — declarative realm configuration
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Docker and Docker Compose
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd compose
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit `.env` with your credentials:
|
||||||
|
|
||||||
|
```env
|
||||||
|
COMPOSE_PROJECT_NAME=pm-keycloak
|
||||||
|
KC_DB_PASSWORD=<your-db-password>
|
||||||
|
KEYCLOAK_ADMIN=admin
|
||||||
|
KEYCLOAK_ADMIN_PASSWORD=<your-admin-password>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Keycloak is available at http://localhost:8080.
|
||||||
|
|
||||||
|
keycloak-config-cli runs once at startup, applies all realm configuration files, then exits. This is expected behavior.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Realm configuration files live in `compose/keycloak-config/`. Each `.yaml` file maps to one realm.
|
||||||
|
|
||||||
|
### Apply configuration changes
|
||||||
|
|
||||||
|
After editing a realm file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose run --rm keycloak-config-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
### File structure
|
||||||
|
|
||||||
|
```
|
||||||
|
compose/keycloak-config/
|
||||||
|
├── master-realm.yaml # minimal patch of the master realm
|
||||||
|
└── demo-realm.yaml # example realm with roles and clients
|
||||||
|
```
|
||||||
|
|
||||||
|
### Managed mode
|
||||||
|
|
||||||
|
`IMPORT_MANAGED_REALM: full` is set, meaning keycloak-config-cli is the source of truth for each realm it manages. Anything not declared in a YAML file will be removed from Keycloak on the next apply.
|
||||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,9 @@
|
|||||||
|
COMPOSE_PROJECT_NAME=pm-keycloak
|
||||||
|
KC_DB_PASSWORD=keycloak
|
||||||
|
KEYCLOAK_ADMIN=admin
|
||||||
|
KEYCLOAK_ADMIN_PASSWORD=admin
|
||||||
|
|
||||||
|
# Client secrets (keycloak-config-cli)
|
||||||
|
DEMO_BACKEND_SECRET=change-me
|
||||||
|
DEMO_USER_PASSWORD=change-me
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
.env
|
||||||
|
postgres_data/
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
services:
|
||||||
|
keycloak:
|
||||||
|
image: quay.io/keycloak/keycloak:26.5.4
|
||||||
|
command: start-dev
|
||||||
|
environment:
|
||||||
|
KC_DB: postgres
|
||||||
|
KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
|
||||||
|
KC_DB_USERNAME: keycloak
|
||||||
|
KC_DB_PASSWORD: ${KC_DB_PASSWORD:-keycloak}
|
||||||
|
KC_HOSTNAME_STRICT: "false"
|
||||||
|
KC_HTTP_PORT: 8080
|
||||||
|
KC_HEALTH_ENABLED: "true"
|
||||||
|
KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_ADMIN:-admin}
|
||||||
|
KC_BOOTSTRAP_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-admin}
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
networks:
|
||||||
|
- compose
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/9000 && echo -e 'GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3 && cat <&3 | grep -q 'UP'"]
|
||||||
|
interval: 15s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 10
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
|
keycloak-config-cli:
|
||||||
|
image: public.ecr.aws/bitnami/keycloak-config-cli:latest
|
||||||
|
platform: linux/amd64
|
||||||
|
environment:
|
||||||
|
KEYCLOAK_URL: http://keycloak:8080
|
||||||
|
KEYCLOAK_USER: ${KEYCLOAK_ADMIN:-admin}
|
||||||
|
KEYCLOAK_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-admin}
|
||||||
|
KEYCLOAK_AVAILABILITYCHECK_ENABLED: "true"
|
||||||
|
KEYCLOAK_AVAILABILITYCHECK_TIMEOUT: 120s
|
||||||
|
IMPORT_FILES_LOCATIONS: /config/*
|
||||||
|
IMPORT_MANAGED_REALM: full
|
||||||
|
DEMO_BACKEND_SECRET: ${DEMO_BACKEND_SECRET}
|
||||||
|
DEMO_USER_PASSWORD: ${DEMO_USER_PASSWORD}
|
||||||
|
BACKLOG_AGENT_SECRET: ${BACKLOG_AGENT_SECRET}
|
||||||
|
A2A_GATEWAY_SECRET: ${A2A_GATEWAY_SECRET}
|
||||||
|
LLM_GATEWAY_SECRET: ${LLM_GATEWAY_SECRET}
|
||||||
|
TOOLS_GATEWAY_SECRET: ${TOOLS_GATEWAY_SECRET}
|
||||||
|
volumes:
|
||||||
|
- ./keycloak-config:/config:ro
|
||||||
|
networks:
|
||||||
|
- compose
|
||||||
|
depends_on:
|
||||||
|
keycloak:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
networks:
|
||||||
|
compose:
|
||||||
|
external: true
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
realm: demo
|
||||||
|
displayName: Demo
|
||||||
|
enabled: true
|
||||||
|
registrationAllowed: false
|
||||||
|
loginWithEmailAllowed: true
|
||||||
|
duplicateEmailsAllowed: false
|
||||||
|
resetPasswordAllowed: true
|
||||||
|
editUsernameAllowed: false
|
||||||
|
bruteForceProtected: true
|
||||||
|
|
||||||
|
clientScopes:
|
||||||
|
- name: app:read
|
||||||
|
description: Read access to application resources
|
||||||
|
protocol: openid-connect
|
||||||
|
- name: app:write
|
||||||
|
description: Write access to application resources
|
||||||
|
protocol: openid-connect
|
||||||
|
|
||||||
|
roles:
|
||||||
|
realm:
|
||||||
|
- name: app-user
|
||||||
|
description: Standard application user
|
||||||
|
- name: app-admin
|
||||||
|
description: Application administrator
|
||||||
|
|
||||||
|
clients:
|
||||||
|
- clientId: demo-app
|
||||||
|
name: Demo Application
|
||||||
|
enabled: true
|
||||||
|
protocol: openid-connect
|
||||||
|
publicClient: true
|
||||||
|
standardFlowEnabled: true
|
||||||
|
directAccessGrantsEnabled: true
|
||||||
|
serviceAccountsEnabled: false
|
||||||
|
redirectUris:
|
||||||
|
- "http://localhost:3000/*"
|
||||||
|
webOrigins:
|
||||||
|
- "http://localhost:3000"
|
||||||
|
defaultClientScopes:
|
||||||
|
- web-origins
|
||||||
|
- acr
|
||||||
|
- profile
|
||||||
|
- roles
|
||||||
|
- email
|
||||||
|
optionalClientScopes:
|
||||||
|
- app:read
|
||||||
|
- app:write
|
||||||
|
protocolMappers:
|
||||||
|
- name: demo-backend-audience
|
||||||
|
protocol: openid-connect
|
||||||
|
protocolMapper: oidc-audience-mapper
|
||||||
|
config:
|
||||||
|
included.client.audience: demo-backend
|
||||||
|
access.token.claim: "true"
|
||||||
|
|
||||||
|
- clientId: demo-app-pkce
|
||||||
|
name: Demo Application (PKCE)
|
||||||
|
enabled: true
|
||||||
|
protocol: openid-connect
|
||||||
|
publicClient: true
|
||||||
|
standardFlowEnabled: true
|
||||||
|
directAccessGrantsEnabled: false
|
||||||
|
serviceAccountsEnabled: false
|
||||||
|
attributes:
|
||||||
|
pkce.code.challenge.method: S256
|
||||||
|
redirectUris:
|
||||||
|
- "http://localhost:3000/*"
|
||||||
|
webOrigins:
|
||||||
|
- "http://localhost:3000"
|
||||||
|
defaultClientScopes:
|
||||||
|
- web-origins
|
||||||
|
- acr
|
||||||
|
- profile
|
||||||
|
- roles
|
||||||
|
- email
|
||||||
|
|
||||||
|
- clientId: demo-backend
|
||||||
|
name: Demo Backend
|
||||||
|
enabled: true
|
||||||
|
protocol: openid-connect
|
||||||
|
publicClient: false
|
||||||
|
standardFlowEnabled: false
|
||||||
|
directAccessGrantsEnabled: false
|
||||||
|
serviceAccountsEnabled: true
|
||||||
|
secret: $(env:DEMO_BACKEND_SECRET)
|
||||||
|
|
||||||
|
users:
|
||||||
|
- username: demo-user
|
||||||
|
email: demo@example.com
|
||||||
|
firstName: Demo
|
||||||
|
lastName: User
|
||||||
|
enabled: true
|
||||||
|
emailVerified: true
|
||||||
|
credentials:
|
||||||
|
- type: password
|
||||||
|
value: $(env:DEMO_USER_PASSWORD)
|
||||||
|
temporary: false
|
||||||
|
realmRoles:
|
||||||
|
- app-user
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Minimal master realm patch — do not remove critical built-in elements
|
||||||
|
realm: master
|
||||||
|
displayName: Master
|
||||||
Reference in New Issue
Block a user