ADR-00007: Identity-Side Mutations via ASR Proxy
Approval Status
Section titled “Approval Status”Status flow: Proposed → Accepted → Reviewed → Approved (or Rejected), or Not Required. A superseded ADR keeps its file; only its status changes to Superseded by NNNNN.
| Governance body | Status | Last update |
|---|---|---|
| CTN Project Team | Proposed | 2026-05-29 |
| CTN Technical Advisory Board | Pending | — |
| CTN Steering Committee | Pending | — |
| BDI Conformance Team | Proposed | 2026-07-08 |
| BDI Framework Team | Pending | — |
Context and Problem Statement
Section titled “Context and Problem Statement”ADR-00003 split the data between two systems:
- Keycloak (the login system) stores everything about a user’s login: their name, email, password, and multi-factor authentication (MFA).
- The ASR (the Association Register, CTN’s own back-end service) is the authoritative record for participants (the registered organisations) and their verifications (the checks that confirm an organisation is who it claims to be).
ADR-00003 also decided that Keycloak keeps track of which users belong to which participant, and what role each user has inside that participant (for example admin, member, or signatory). Keycloak does this with its built-in Organizations feature — one Keycloak Organization per participant.
This creates a practical question: when a user updates their name or email, or when an admin removes a member, or when a participant admin promotes a member to admin — who calls Keycloak?
Three constraints apply:
- The portal must not call Keycloak directly. Direct portal-to-Keycloak calls bypass ASR’s audit trail and business rules, and expose Keycloak Admin API surface to the browser.
- Business rules belong in ASR, not split across two systems. Examples: “a participant must always have at least one admin”; “an email change requires re-verification”; “removing the last member dissolves the participant” (or doesn’t — that itself is an ASR-side rule).
- The earlier prototype dead-end (M21) must not recur: user profile read-only in the portal because all mutations require Keycloak admin intervention.
This ADR decides how user-identity-side mutations are performed from the portal, given that the data lives in Keycloak.
Considered Options
Section titled “Considered Options”Option 1: ASR proxy endpoints calling Keycloak Admin API (service-account)
Section titled “Option 1: ASR proxy endpoints calling Keycloak Admin API (service-account)”- Description: ASR exposes endpoints such as
PATCH /users/me,POST /participants/{id}/members,DELETE /participants/{id}/members/{userId},PUT /participants/{id}/members/{userId}/role. ASR authorizes the request against participant context, applies business rules, then calls Keycloak Admin API (and the Organizations API for membership/groups) using a service account. All actions land in ASR’s audit log. - Pros: Single enforcement point for authorization, business rules, and audit. Portal sees one API. Matches the single-writer pattern that the an earlier partner review assessed as solid (O4). Mutations are atomic from the portal’s perspective.
- Cons: ASR holds powerful Keycloak credentials — high-blast-radius if compromised. Service-account scope must be tightly minimised. ASR must implement compensating rollback if a multi-step operation partially fails.
Option 2: Direct portal → Keycloak Account Console / Account API
Section titled “Option 2: Direct portal → Keycloak Account Console / Account API”- Description: For user-self-service mutations only (own profile, own password, own MFA), the portal redirects to or embeds Keycloak’s Account Console. Participant-side mutations still go through ASR.
- Pros: No code in ASR for password/MFA flows; uses Keycloak’s hardened built-in UX. Reduces ASR’s blast radius.
- Cons: UX split across two surfaces. ASR loses audit visibility on identity-side changes. Business rules that span identity and participant (e.g. email change triggers verification) become harder to enforce. Violates the “portal does not call Keycloak directly” guidance from ADR-00003.
Option 3: Hybrid — Account Console for secrets, ASR proxy for everything else
Section titled “Option 3: Hybrid — Account Console for secrets, ASR proxy for everything else”- Description: Password reset and MFA setup go to Keycloak’s native flows (where the security-critical handling is). Profile fields (name, email), membership, and role changes go through ASR proxy endpoints. ASR subscribes to Keycloak admin events to ingest password/MFA changes into its audit log.
- Pros: Best-of-both — leverages Keycloak’s hardened auth flows for secrets, keeps everything else in ASR. Audit completeness via event subscription.
- Cons: Two integration surfaces to maintain. Event-driven audit ingestion adds a moving part. Requires careful design of which paths go where.
Scope of Mutations
Section titled “Scope of Mutations”Regardless of option, the following mutations need an explicit path. This list scopes the design space.
The mutation scope depends on the user’s identity source, defined per participant type in ADR-00008. Mutations that target user-level identity (name, email, password, MFA, lifecycle) are only ASR’s concern for users on a local Keycloak account. For Entra-federated users (e.g. Contargo) those mutations live in the participant’s Entra tenant; ASR must not attempt to override them.
| Mutation | Touches | Local-account user | Entra-federated user (e.g. Contargo) |
|---|---|---|---|
| Update own profile (name) | Keycloak user | ASR proxy → Keycloak Admin API | Out of scope — managed in participant’s Entra |
| Update own email | Keycloak user (+ re-verification in ASR) | ASR proxy → Keycloak Admin API | Out of scope — Entra is source of truth; ASR re-reads on next login |
| Change password | Keycloak credential | ASR-fronted or Keycloak Account Console (per chosen option) | Not applicable — participant’s Entra owns credentials |
| Enroll / reset MFA | Keycloak credential | ASR-fronted or Keycloak Account Console (per chosen option) | Not applicable — participant’s Entra owns MFA |
| Invite a new member to a participant | Keycloak (create user / send invite) + Keycloak Organization (add member) | ASR proxy creates Keycloak user + adds to Organization | ASR proxy adds existing Entra user to the Organization (JIT or pre-invite per ADR-00008) |
| Remove a member from a participant | Keycloak Organization (remove member) — possibly Keycloak user | ASR proxy removes from Organization; deletes Keycloak user if no other memberships | ASR proxy removes from Organization only; Keycloak federated identity record left as it tracks an Entra sub |
| Promote a member to admin | Keycloak Organization Group (/admin) | ASR proxy → Organizations Admin API | ASR proxy → Organizations Admin API |
| Transfer the admin role | Keycloak Organization Group | ASR proxy → Organizations Admin API | ASR proxy → Organizations Admin API |
| Deactivate a participant | ASR (status) + Keycloak Organization (disable) + member access propagation | ASR proxy disables Organization; all members lose participant context | Same — disabling the Organization breaks federated-user membership without touching participant’s Entra |
Decision Outcome
Section titled “Decision Outcome”Chosen option: TBD.
Rationale
Section titled “Rationale”TBD.
Consequences
Section titled “Consequences”- Positive: TBD.
- Negative: TBD.
- Neutral: TBD.
Open Questions
Section titled “Open Questions”-
For email changes (local accounts): is the new email verified before or after it is written to Keycloak? What is the user’s effective email during the transition window?
- RdN Proposal: before (that also discourages script-kiddies trying to register many users by means of a bot)
-
For email changes (Entra-federated): does ASR re-sync on every login, or only on explicit user action? Stale display-name/email is a UX concern only — authoritative identity stays at Entra.
- RdN Proposal: Every login. In reality the number of times that (entra-federated) users will actually login to the ASR will be very limited.
-
“Last admin cannot be removed” — is this a hard constraint or does the ASR allow a temporary admin-less state with an SLA to resolve? The same question applies to a participant whose only remaining admin is offboarded at Entra.
- RdN Proposal: An edge case, which can be resolved by the ASR admin manually for now. Btw, this is a question I would expect to see on the Jira board.
-
Does removing a user from their last participant delete the Keycloak user (local account), or leave a dormant identity? For Entra-federated, the Keycloak federated-identity record likely stays — its uniqueness key is the Entra
sub, useful if the same user is re-invited later.- RdN Question: I don’t grasp this question.
-
Which Keycloak admin events do we subscribe to, and where do we store them? Of particular interest: federated login failures that indicate source-IdP deprovisioning, since those are the lifecycle signal for Entra-federated users.
- RdN Question: Didn’t we agree on a basic (audit) loggin mechanism. In this case it is a system-triggered event which should end up in the log and result in a notification to the ASR admin.
-
How do we scope the Keycloak service account so that compromise of ASR cannot escalate to full realm control?
- RdN Question: Please advise.
Implementation Plan
Section titled “Implementation Plan”- Phase 1: Confirm the mutation scope above with stakeholders; close gaps.
- Phase 2: Pick an option and define the concrete API surface (one endpoint per mutation, with authorization and audit requirements).
- Phase 3: Define the Keycloak service-account least-privilege role mapping. Document the rollback strategy for multi-step mutations.
Compliance and Validation
Section titled “Compliance and Validation”- Security review completed
- Performance impact assessed
- Integration impact evaluated
- Documentation updated
- Stakeholder approval obtained
Related Documents
Section titled “Related Documents”- 00002-define-a-preferred-stack.md
- 00003-separate-users-and-participants.md
- 00006-token-claim-composition.md
- 00008-external-idp-federation-strategy.md (mutation scope per participant type derives from the federation strategy defined there)
- an earlier CTN-ASR Final Delivery Review, 2026-03-27 (findings M9, M18, M19, M20, M21 — organisation management gaps in the prototype)
- Keycloak Admin REST API documentation
- Keycloak blog, “Organization Groups” (2026-04-29): https://www.keycloak.org/2026/04/org-groups
Status History
Section titled “Status History”| Date | Status | Notes |
|---|---|---|
| 2026-05-29 | Proposed | Skeleton — mutation scope listed, options outlined, decision pending |
| 2026-05-29 | Proposed | Mutation scope split per participant type (local-account vs Entra-federated), driven by ADR-00008 |
In samenwerking met




