SSO-Rijk → RIG Platform Realm (sso-rijk IDP) → Project Realms (optional federation)
Bootstrap is automatic via opi/bootstrap/keycloak_setup.py on startup: creates rig-platform realm, configures SSO-Rijk IDP, creates client scopes and protocol mappers, creates the operations manager OIDC client. All operations are idempotent.
- Alias:
sso-rijk, Type: OIDC - Discovery URL: From
KEYCLOAK_MASTER_OIDC_DISCOVERY_URLenv var - Auth flow: External IDP Redirector - auto-redirects to SSO-Rijk (no username/password prompt)
- Platform realm uses this flow as default browser flow; project realms use standard "browser" flow
IDP mappers capture incoming claims from SSO-Rijk and store them as user attributes:
| Mapper Name | Source Claim | Target Attribute | Sync Mode |
|---|---|---|---|
| email-to-username | email |
username | INHERIT |
| email-mapper | email |
email |
INHERIT |
| first-name-mapper | given_name |
firstName |
INHERIT |
| last-name-mapper | family_name |
lastName |
INHERIT |
| full-name-mapper | name |
displayName |
INHERIT |
| organization-number-mapper | organization.number |
organization.number |
INHERIT |
| organization-name-mapper | organization.name |
organization.name |
INHERIT |
| sso-rijk-userid-mapper | sub |
sso-rijk-userid |
FORCE |
| sso-rijk-userid-lowercase-mapper | preferred_username |
sso-rijk-userid-lowercase |
FORCE |
Note: SSO-Rijk mappers use FORCE sync mode to ensure attributes are always current.
The custom_attributes_passthrough scope is assigned as a realm-level default scope, ensuring all clients automatically include custom attributes in tokens.
To enable group-based authorization, clients need to include user group memberships in access tokens.
Configuration Steps:
- Navigate to Client → Select your client (e.g.,
authentication-client) - Client scopes tab → Dedicated scopes (or the client name link)
- Mappers tab → Add mapper → By configuration → Group Membership
- Configure the mapper:
- Name:
groups - Token Claim Name:
groups - Full group path: OFF (important! otherwise groups include leading slash
/RIGinstead ofRIG) - Add to ID token: ON
- Add to access token: ON
- Add to userinfo: ON
- Name:
- Save
Result: Access tokens will include a groups claim with user group memberships:
{
"groups": ["RIG", "ICTU"],
...
}Important: Ensure "Full group path" is disabled, otherwise groups will have format /RIG which won't match organization codes in the database.
Protocol Mappers:
| Mapper Name | User Attribute | Token Claim | Purpose |
|---|---|---|---|
| Organization Name Passthrough | organization.name |
organization.name |
Custom claim |
| Organization Number Passthrough | organization.number |
organization.number |
Custom claim |
| SSO-Rijk UserID Override (sub) | sso-rijk-userid |
sub |
Override standard sub claim |
| SSO-Rijk UserID Lowercase Override | sso-rijk-userid-lowercase |
preferred_username |
Override standard username |
| SSO-Rijk UserID Passthrough | sso-rijk-userid |
sso-rijk-userid |
Custom claim |
| SSO-Rijk UserID Lowercase Passthrough | sso-rijk-userid-lowercase |
sso-rijk-userid-lowercase |
Custom claim |
Key Behavior: Platform realm overrides standard sub and preferred_username claims with SSO-Rijk values for transparent SSO.
Project realms use the same custom_attributes_passthrough scope but without override mappers:
- Organization attributes: Passed through as custom claims
- SSO-Rijk attributes: Passed through as custom claims only
- Standard claims (
sub,preferred_username): Use Keycloak's internal values
Platform realm overrides sub and preferred_username claims with SSO-Rijk values, so apps get consistent token claims regardless of whether Digilab is in the chain:
sub: SSO-Rijk NameID (e.g.,urn:collab:person:minbzk:nl:Uittenbroek)preferred_username: Lowercase version
- Operations Manager:
rig-platform-operations-manager(confidential, auto-configured during bootstrap) - Project clients:
{project-name}-{deployment-name}, created bykeycloak_manager.py, credentials stored in K8s secrets
KEYCLOAK_URL=http://keycloak.kind
KEYCLOAK_ADMIN_USERNAME=admin
KEYCLOAK_ADMIN_PASSWORD=<admin-password>
KEYCLOAK_DEFAULT_REALM=rig-platform
KEYCLOAK_MASTER_OIDC_CLIENT_ID=<client-id>
KEYCLOAK_MASTER_OIDC_CLIENT_SECRET=<client-secret>
KEYCLOAK_MASTER_OIDC_DISCOVERY_URL=<discovery-url>opi/bootstrap/keycloak_setup.py- automatic configuration on startupopi/connectors/keycloak.py- API clientopi/manager/keycloak_manager.py- project client management
kubectl logs -n rig-system deployment/operations-manager | grep "Step"Check that the External IDP Redirector flow is configured:
# In Keycloak Admin UI:
# 1. Select rig-platform realm
# 2. Authentication → Flows → External IDP Redirector
# 3. Verify "Identity Provider Redirector" has config with defaultProvider: sso-rijkSymptom: Realm shows "Invalid username or password" without displaying login form, or automatically redirects to external SSO when you want local authentication.
Cause: The realm's browser flow is bound to "External IDP Redirector" instead of the standard "browser" flow.
Solution: Change the browser flow binding in Keycloak Admin UI:
- Login to Keycloak master realm at
http://keycloak.kind/admin/master/console/ - Switch to the target realm (top-left dropdown)
- Navigate to Authentication in the left sidebar
- Select the "browser" flow from the list
- Click the Actions menu (⋮) for that flow
- Click "Bind flow"
- Confirm the binding
When to Use:
- Project realms should use "browser" flow for local username/password authentication
- Platform realm (
rig-platform) uses "External IDP Redirector" for automatic SSO redirect to SSO-Rijk
Try accessing the operations manager - it should redirect directly to SSO-Rijk without showing a username/password prompt.
In Keycloak Admin UI:
Realm Settings → Client Scopes → Default Client Scopes
→ Verify "custom_attributes_passthrough" is in the "Assigned default client scopes" list
Symptom: Application returns "502 Bad Gateway" intermittently, especially during OAuth authentication flow.
Root Cause: OAuth authentication generates large response headers that exceed nginx's default buffer size (4k-8k):
- CSRF tokens
- OAuth state cookies
- Session identifiers
- User attribute cookies
- Application custom headers
Why Intermittent:
- First visit: Small headers → works fine
- OAuth callback: Multiple cookies set simultaneously → headers exceed buffer → 502 error
- Browser accumulates cookies across requests, compounding the problem
curlrequests work (no cookies) while browser requests fail (cookies persist)
Check the Error:
kubectl logs -n ingress-nginx deployment/ingress-nginx-controller | grep "upstream sent too big header"Solution: Increase nginx buffer sizes via ingress annotations (already configured in manifests/ingress.yaml.jinja):
annotations:
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
nginx.ingress.kubernetes.io/proxy-buffers-number: "4"Temporary Fix for Existing Ingress:
kubectl annotate ingress <ingress-name> \
nginx.ingress.kubernetes.io/proxy-buffer-size="16k" \
nginx.ingress.kubernetes.io/proxy-buffers-number="4" \
--overwrite