Files
register-referentie/docs/runbooks/keycloak.md
T
notandClaude Opus 5 a87a32e269
CI / build (pull_request) Successful in 1m10s
CI / lint (pull_request) Successful in 1m27s
CI / unit (pull_request) Successful in 1m24s
CI / frontend (pull_request) Successful in 3m27s
CI / mutation (pull_request) Successful in 6m29s
CI / verify-stack (pull_request) Successful in 9m41s
docs(infra): document MFA on the medewerker realm + ADR-0031 (refs #132)
Runbook gains an MFA section and how to get a code; synthetic-data lists the fixture
TOTP secret and the extra grant parameter; demo-script gains the S-15c note and its
staff logins now mention the second factor. check_realms.py grows an 'otp' argument
that prints a current code for a manual demo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 09:11:03 +02:00

2.9 KiB

Keycloak runbook

Keycloak (infra/keycloak/docker-compose.yml) runs in dev mode with four realms imported at boot from infra/keycloak/realms/: digid, eherkenning, eidas, medewerker. It mocks the Dutch identity brokers so portals can do real OIDC logins locally. Host port :8180.

Quick test (make)

make keycloak-up      # start Keycloak + import realms (~30-60s first boot)
make keycloak-smoke   # start + verify every realm logs in and returns its claim
make keycloak-down    # stop + wipe

make keycloak-smoke runs infra/keycloak/check_realms.py, which does a password-grant login per realm and asserts the identifying claim:

Realm User Claim asserted
digid jan-burger bsn
eherkenning acme-ondernemer kvk
eidas pierre-dupont eidas_id
medewerker merel-behandelaar role behandelaar

The medewerker row also asserts that the password alone is refused — that realm enforces MFA (below).

All test users / credentials are in ../synthetic-data.md.

Notes

  • Admin console: http://localhost:8180/admin / admin (dev only).
  • Client big-portal is public with standardFlowEnabled (browser redirect login) and directAccessGrantsEnabled (password grant, used by the smoke test).
  • Dev store: in-memory H2 via start-dev; realms re-import on each boot, so changes made in the admin UI don't persist. Edit the realm JSONs to make durable changes.
  • Image pinned to quay.io/keycloak/keycloak:26.1.
  • Claims are injected by OIDC protocol mappers on big-portal (user attribute → token claim); medewerker roles come through realm_access.roles.

MFA on the medewerker realm (S-15c)

Staff logins (behandel + beheer portals) need a second factor; citizen/company realms (digid, eherkenning, eidas) do not. Two halves in medewerker-realm.json:

  • Every seeded medewerker carries a TOTP credential with the fixture secret BIGMEDEWERKEROTPSEED, so Keycloak's built-in conditional OTP step fires on every login — browser flow (an #otp prompt after the password) and direct grant (a totp form field) alike.
  • CONFIGURE_TOTP is a default required action, so any medewerker added later must enrol an authenticator before the first login.

See ../architecture/adr-0031-mfa-on-the-medewerker-realm.md.

Getting a code

python3 infra/keycloak/check_realms.py otp   # prints a valid 6-digit code right now

Or enrol a phone once: the secret in base32 is IJEUOTKFIRCVORKSJNCVET2UKBJUKRKE (otpauth://totp/medewerker?secret=IJEUOTKFIRCVORKSJNCVET2UKBJUKRKE). The e2e computes its own code in tests/e2e/medewerker-login.ts.

Fixture only. A shared, committed secret is a demo convenience, never a production posture — see the ADR's consequences.