ADR-00003: Separate login identities (users) from participant organisations
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 | Approved | 2026-05-29 |
| CTN Technical Advisory Board | Approved | 2026-07-08 |
| CTN Steering Committee | Not Required | 2026-07-08 |
| BDI Conformance Team | Proposed | 2026-07-08 |
| BDI Framework Team | Pending | — |
Terminology (read this first)
Section titled “Terminology (read this first)”Two words in this ADR mean something specific. To avoid confusion:
- User = a login identity (an account that can authenticate), not necessarily a person. A user is either:
- a person logging in on behalf of an organisation (a “human-to-machine” or H2M login — for example an employee using the participant portal), or
- a system/application logging in without a human (a “machine-to-machine” or M2M login — for example another company’s IT system calling the API).
- Participant = an organisation (a legal entity, e.g. a company) that has been registered in the CTN Association Register. A participant is not a person; people and systems act on behalf of a participant.
So the relationship is: one participant (organisation) has one or more users (people and/or systems) that act for it.
Terminology note: CTN no longer uses the word “member”; the correct term for an onboarded organisation is participant (see ADR-00004: Naming follows glossary and the glossary). “Member” survives only as a Keycloak role name (
admin / member / signatory) within a participant.
Context and Problem Statement
Section titled “Context and Problem Statement”The Association Register (ASR) has to keep track of two different kinds of things: users (login identities — people or systems, see Terminology above) and participants (the organisations they act for). These are fundamentally different: a login identity is about authentication (“who is signing in?”), while a participant is about business registration (“which verified organisation is this?”).
Keycloak has been selected as the identity and access management (IAM) solution (see ADR-00002). The question this ADR answers is: where do we store user data, and where do we store participant data? We want a clean separation of concerns, minimal data duplication, and no fragile synchronisation logic between Keycloak and the ASR service — without constraining the ASR’s own participant logic and onboarding workflows.
Considered Options
Section titled “Considered Options”Option 1: Clean Separation (Users in Keycloak, participants in ASR)
Section titled “Option 1: Clean Separation (Users in Keycloak, participants in ASR)”- Description: Store all user-related identification and authentication data in Keycloak. Store all participant-related data in the ASR service’s database. Link them using unique identifiers (e.g., Keycloak User IDs).
- Pros:
- Clean separation of concerns.
- No data synchronization required between systems.
- Leverages Keycloak’s strengths in identity management.
- ASR service remains focused on participant logic.
- Cons: Requires cross-referencing between two systems for certain operations.
Option 2: Unified Storage in ASR
Section titled “Option 2: Unified Storage in ASR”- Description: Store both users and participants in the ASR service database, using Keycloak only for authentication.
- Pros: All entity data is in one place.
- Cons: Duplicates user data that Keycloak already manages. Increases complexity in the ASR service.
Option 3: Unified Storage in Keycloak
Section titled “Option 3: Unified Storage in Keycloak”- Description: Store participant data as attributes or groups within Keycloak.
- Pros: Single source of truth for all entities.
- Cons: Keycloak is not designed as a general-purpose participant registry. Management of complex participant hierarchies and metadata would be cumbersome and limited.
Decision Outcome
Section titled “Decision Outcome”Chosen option: Option 1: Clean Separation. We store login identities (users) in Keycloak and participant organisations in the ASR, linked by ID.
Rationale
Section titled “Rationale”This approach follows the principle of using the right tool for the job. Keycloak is excellent for identity and authentication, while the ASR service is specifically designed to manage participant structures and business logic.
By separating them, we achieve several key benefits:
- Avoiding Pollution: We prevent Keycloak from being cluttered with ASR-specific metadata, complex participant hierarchies, and domain-specific logic that doesn’t belong in an IAM solution.
- Scalability: A separate ASR service can be scaled independently of Keycloak, allowing us to handle high volumes of participant data and complex queries without impacting the performance of the authentication system.
- Custom Onboarding: A dedicated service allows us to implement custom onboarding workflows for participants (e.g., verification steps, business rule checks) that go beyond the standard user registration and IAM onboarding flows.
- Reduced Complexity: By linking them via IDs and avoiding synchronization, we reduce system complexity and the risk of data inconsistency. This aligns with our goal of keeping the architecture clean and maintainable.
The clean separation pattern was validated in the an earlier CTN-ASR prototype (final delivery review, 2026-03-27): the Keycloak integration itself was assessed as solid (single-writer pattern, service-account provisioning, BFF cookie auth, rollback compensation). The issues that surfaced in that prototype (fabricated EORI numbers in tokens, missing trust levels, unpredictable claim structure, profile mutation dead-ends) were not caused by the separation itself but by violations of it — pushing business identifiers and verification status into Keycloak as attributes instead of treating Keycloak as identity-only. The scope rules below codify those lessons.
ID Ownership and Linking
Section titled “ID Ownership and Linking”To maintain a clear boundary, we define strict ownership of identifiers:
- User IDs are owned and generated by Keycloak. These are the unique identifiers used across the system to refer to a specific identity.
- Participant IDs are owned and generated by the ASR service.
Scope of Data per System
Section titled “Scope of Data per System”The separation is only meaningful if both sides respect what each system is for. The following rules apply:
| Lives in Keycloak (identity) | Lives in ASR (participants) |
|---|---|
| User credentials, MFA, password reset state | Participant entity (legal entity record) |
| Minimal user profile (email, name) | Business identifiers: KVK / KBO / HRB, EUID, LEI, EORI, VAT |
| Authentication sessions, refresh tokens | Verification records and their status (Pending / Approved / Rejected / Revoked) |
| Role / group membership for authentication purposes | Trust level and any derived assurance score |
| Participant↔user membership and per-participant roles (admin / member / signatory) | |
| Onboarding workflow state, uploaded documents, audit trail |
Anti-pattern (from an earlier prototype, to avoid): writing business identifiers or verification status into Keycloak as user/organization attributes so they “come along for free” in the access token. This duplicates the source of truth in ASR, invites drift, and — as the prototype demonstrated — leads to fabricated values being presented as authoritative in tokens. Token enrichment with participant attributes is a separate concern: claims should be composed from ASR at token-issuance or token-exchange time, not by mirroring ASR data into Keycloak storage.
Use of Keycloak Organizations
Section titled “Use of Keycloak Organizations”Keycloak 26.6 ships a first-class Organizations feature with hierarchical Organization Groups (per-organization isolated, no realm-level collisions), per-organization IdP linking, a built-in organization token scope, and a dedicated Admin REST API. This is complementary to — not a replacement for — the separation defined above. We adopt it for the IAM-side concerns that would otherwise require us to reinvent membership and federation in ASR:
| Concern | Implementation |
|---|---|
| User-to-participant membership | A Keycloak Organization exists per ASR participant. The user’s membership of that Keycloak Organization is the membership relation. ASR does not maintain a parallel join table. |
| Per-participant role (admin / member / signatory) | Modeled as Keycloak Organization Groups (e.g. /admin, /signatory). Surfaced in tokens via the Organization Group Membership mapper. |
| Per-participant identity federation (e.g. the participant’s own Entra ID, eHerkenning, eIDAS) | A federated IdP linked to the Keycloak Organization, with Hardcoded Group or Advanced Claim to Group mappers as needed. The federation strategy per participant type is defined in ADR-00008. |
| Linking back to ASR | The Keycloak Organization’s alias (or a dedicated attribute) carries the ASR participant ID; the ASR participant record stores the Keycloak Organization ID. This is the only cross-system reference; no other duplication. |
Reinforced anti-patterns specific to Keycloak Organizations:
- No business identifiers as Keycloak Organization attributes. KVK / KBO / HRB / EUID / LEI / EORI / VAT remain authoritative in ASR. Putting them on the Keycloak Organization recreates an earlier prototype’s class of bug (fabricated values, drift, no verification context).
- The built-in
organizationclaim shape is alias-as-key ({"organization": {"acme-corp": {...}}}). This is the same anti-pattern as M17 from the prototype review — consumers cannot rely on a predictable JSON path. A separate ADR will decide whether we accept this shape and document the convention, or emit a custom claim structure via a protocol mapper.
Known limitation: Keycloak Organization Groups cannot be used directly in Keycloak’s own authorisation policies. Fine-grained authorisation is owned by the data provider in 2026 (see Solution Strategy §3); when a separate PDP service is introduced in a later phase it would consume the same Keycloak claims.
Mutation Paths
Section titled “Mutation Paths”Because participant-side data does not live in Keycloak, the ASR service must expose explicit endpoints for any data the user is allowed to change that crosses the boundary:
- User-owned identity fields (name, email, password) are mutated via ASR endpoints that proxy to the Keycloak Admin API. The portal does not call Keycloak directly.
- Participant data is mutated via ASR endpoints, with authorization scoped to the participant the user belongs to.
- Membership and per-participant role assignments are mutated via the Keycloak Organizations Admin REST API, fronted by ASR endpoints so that authorization, audit, and business rules (e.g. “cannot remove the last admin”) remain in one place.
This avoids the “profile is read-only” dead-end observed in the prototype.
Addressing the Chicken-and-Egg Problem
Section titled “Addressing the Chicken-and-Egg Problem”A potential “chicken-and-egg” problem arises during the onboarding of a new participant by a new user: does the user exist before the participant, or vice versa?
We resolve this by defining the following flow:
- User Identity First: The natural person must first be registered in Keycloak to establish a verifiable identity.
- Participant Creation: Once authenticated, the user (now possessing a Keycloak User ID) can initiate the participant onboarding process in the ASR service.
- Linking: The ASR service creates the participant record and stores the Keycloak User ID as the “owner” or “initial administrator” of that participant.
This ensures that we always have a valid identity to attribute participant actions to, avoiding orphan participants or unauthenticated registration attempts.
Consequences
Section titled “Consequences”- Positive:
- Reduced architectural complexity by avoiding sync logic.
- Improved data integrity by having a single source of truth for each entity type (Keycloak for identity, ASR for participants and verifications).
- Scalable approach for both user and participant management.
- Verification status and trust level remain authoritative in ASR — avoids the “fabricated identifier in token” class of bug seen in the prototype.
- Negative:
- Application logic must handle the resolution of IDs between Keycloak and ASR when both user and participant details are needed.
- ASR must expose explicit endpoints for any identity-side mutation the user is allowed to perform (proxying to Keycloak Admin API), rather than relying on Keycloak’s account console.
- Token enrichment with participant claims requires a deliberate composition step (subject of a future ADR) instead of falling out of Keycloak storage automatically.
- Neutral:
- Internal APIs will need to support fetching details by ID across service boundaries.
Implementation Plan
Section titled “Implementation Plan”- Phase 1: Design the ASR database schema to include participant entities with references to Keycloak User IDs where necessary.
- Phase 2: Implement the ASR service to manage participant CRUD operations.
- Phase 3: Configure Keycloak to provide User IDs in tokens or via APIs for the ASR service to consume.
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
- 00006-token-claim-composition.md (decides the claim shape for participant context — addresses the alias-as-key issue raised here)
- 00007-identity-mutation-proxy.md (defines the ASR endpoints that proxy to Keycloak for identity-side mutations)
- 00008-external-idp-federation-strategy.md (per-participant-type federation: Entra ID / eHerkenning / local)
- an earlier CTN-ASR Final Delivery Review, 2026-03-27 (findings C9, M16, M17, M21 informed the scope and mutation-path sections of this ADR).
- Keycloak blog, “Organization Groups: Structure Your Organizations with Hierarchical Group Management” (2026-04-29): https://www.keycloak.org/2026/04/org-groups
Status History
Section titled “Status History”| Date | Status | Notes |
|---|---|---|
| 2026-05-28 | Proposed | Initial proposal |
| 2026-05-29 | Proposed | Reinforced with lessons from an earlier prototype review: explicit scope-of-data rules and mutation-path guidance added |
| 2026-05-29 | Proposed | Added “Use of Keycloak Organizations” section adopting Keycloak 26.6 Organizations + Organization Groups for membership, per-participant roles, and per-participant IdP federation |
In samenwerking met




