Skip to content
Deze documentatie wordt actief uitgebreid — kom regelmatig terug.

Building Block View - CTN Components

The 2026 delivery is centred on the Association Register (ASR): participant management, identifier enrichment, VAD issuance, endpoint registration, and discovery. Authorisation stays local at the data provider. There is no central CTN policy decision point in 2026; each provider validates the VAD and applies its own policy.

Components for later phases — Orchestration Register, dossier-bound VOD tokens, OPA-based policy engine, orchestrator portal federation — have their design notes parked under docs/_out-of-scope-2026/ (gitignored). See the Roadmap for when they come back into scope.

Association Register - Detailed Architecture

Section titled “Association Register - Detailed Architecture”

The CTN Association Register system consists of three main building blocks providing member management, self-service portals, and API services.

graph TB
    subgraph "CTN Association Register"
        A[Admin Portal]
        B[Participant Portal]
        C[API Layer]
        D[Database]
    end

    E[CTN Admin Users] -->|Manage Members| A
    F[CTN Member Users] -->|Self-Service| B

    A --> C
    B --> C
    C --> D

    G[Keycloak] -.->|Authentication| A
    G -.->|Authentication| B

    H[KvK API] -.->|Verification| C
  
ComponentResponsibilityTechnology
Admin PortalCTN staff interface for participant managementFrontend stack to be decided (see Coding Standards → Open Questions); a React + TypeScript SPA is one candidate
Participant PortalSelf-service interface for participant organisationsSame — frontend stack TBD
API LayerBusiness logic, data access, external integrationsQuarkus on Java 25+ LTS, packaged as OCI image
DatabasePersistent data storagePostgreSQL 15+ (managed; Azure Flexible Server in the reference deployment, RDS / Crunchy as alternatives)
graph LR
    subgraph "Admin Portal"
        A[Authentication Module]
        B[Member Management]
        C[Legal Entity Management]
        D[Identifier Management]
        E[Contact Management]
        F[Endpoint Management]
        G[Audit Log Viewer]
    end

    A --> B
    A --> C
    A --> D
    A --> E
    A --> F
    A --> G

    B --> H[API Client]
    C --> H
    D --> H
    E --> H
    F --> H
    G --> H

    I[Keycloak] -.-> A

Responsibility: User login, session management, token refresh

Key Components (framework-agnostic):

  • Auth state/context — holds the current session
  • Keycloak integration module (OIDC)
  • Route guards for protected views

Dependencies:

  • Keycloak OIDC adapter for the chosen frontend stack (e.g. keycloak-js for a JavaScript portal)
  • Keycloak realm/client configuration

Responsibility: Member CRUD operations, status changes, approval workflows

Key Components:

  • Participants grid — data table for participant listing
  • Participant detail view
  • Approval workflow — multi-step approval process

API Endpoints:

  • GET /v1/participants — list participants (paginated)
  • GET /v1/participants/{participantId} — get participant details
  • POST /v1/participants — create participant
  • PUT /v1/participants/{participantId} — update participant
  • DELETE /v1/participants/{participantId} — delete participant

Responsibility: Legal entity verification, KvK integration, international registries

Key Components:

  • Legal-entity form — entity creation/editing
  • KvK lookup — KvK number search widget
  • Verification-status indicator

API Endpoints:

  • POST /v1/legal-entities - Create legal entity
  • GET /v1/legal-entities/{legalEntityId} - Get entity details
  • PUT /v1/legal-entities/{legalEntityId}/verify - Trigger KvK verification
graph LR
    subgraph "Participant Portal"
        A[Authentication Module]
        B[Organization Profile]
        C[Contact Management]
        D[Endpoint Management]
        E[Identifier Management]
        F[Support Page]
    end

    A --> B
    A --> C
    A --> D
    A --> E
    A --> F

    B --> G[API Client]
    C --> G
    D --> G
    E --> G

    H[Keycloak] -.-> A

Responsibility: View and update organization information

Key Components:

  • Profile view — organisation profile display
  • Profile edit form — editable organisation fields
  • Identifier list — display registered identifiers

Features:

  • Read-only fields: KvK number, legal entity name
  • Editable fields: Address, phone, email
  • Auto-save on field blur

Responsibility: Register and manage API endpoints

Key Components:

  • Endpoint grid — list of registered endpoints
  • Endpoint form — add/edit endpoint details
  • Health-status indicator

Validation:

  • URL format validation (HTTPS required)
  • Duplicate endpoint detection
  • Version format validation (semver)
graph TB
    subgraph "API Layer (Quarkus on Java 25+)"
        A[REST Resources]
        B[Authentication quarkus-oidc]
        C[Business Logic Services]
        D[Data Access Layer Panache]
        E[External Integrations]
    end

    F[Admin Portal] --> A
    G[Participant Portal] --> A

    A --> B
    B --> C
    C --> D
    C --> E

    D --> H[PostgreSQL]
    E --> I[KvK API]
    E --> J[BDI Framework]

    K[Keycloak] -.->|Token Validation| B
graph LR
    subgraph "REST Resources (JAX-RS)"
        A[ParticipantResource]
        B[LegalEntityResource]
        C[IdentifierResource]
        D[ContactResource]
        E[EndpointResource]
    end

    G[HTTP Request] --> A
    G --> B
    G --> C
    G --> D
    G --> E

ParticipantResource:

  • GET /v1/participants - List/search participants
  • GET /v1/participants/{participantId} - Retrieve single participant
  • POST /v1/participants - Register new participant
  • PUT /v1/participants/{participantId} - Modify participant details
  • DELETE /v1/participants/{participantId} - Remove participant (cascade delete)

LegalEntityResource:

  • POST /v1/legal-entities - Register legal entity
  • GET /v1/legal-entities/{legalEntityId} - Retrieve entity
  • PUT /v1/legal-entities/{legalEntityId}/verify - Trigger KvK verification

IdentifierResource:

  • POST /v1/legal-entities/{legalEntityId}/identifiers - Register identifier (KvK, EUID, LEI, etc.)
  • GET /v1/legal-entities/{legalEntityId}/identifiers - List identifiers for a legal entity
  • PUT /v1/legal-entities/{legalEntityId}/identifiers/{identifierId} - Modify identifier metadata
  • DELETE /v1/legal-entities/{legalEntityId}/identifiers/{identifierId} - Remove identifier

Token (VAD) endpoints:

  • GenerateVAD - Generate VAD token
  • GET /.well-known/jwks.json - Retrieve RSA public keys for verification (JWKS)

Responsibilities:

  • Validate the bearer JWT against Keycloak (signature, expiry) as an OIDC resource server
  • Extract user claims (roles, email, organization)
  • Enforce @Authenticated / @RolesAllowed on resources
  • Return 401 Unauthorized if validation fails

Implementation:

  • quarkus-oidc resource-server configuration (see Coding Standards §4.3)
  • JWKS caching (~10 minutes; rotation handled via Keycloak’s key-rotation events)
  • Role extraction for RBAC
graph TB
    subgraph "Services Layer"
        A[ParticipantService]
        B[LegalEntityService]
        C[IdentifierService]
        D[ContactService]
        E[EndpointService]
        F[TokenService]
    end

    A --> G[Database Repository]
    B --> G
    C --> G
    D --> G
    E --> G

    F --> H[Crypto Service]
    F --> I[JWT Service]

    B --> J[KvK Integration]

Service Responsibilities:

ServiceResponsibilityExternal Dependencies
ParticipantServiceParticipant CRUD, status management, approval workflowNone
LegalEntityServiceEntity creation, KvK verificationKvK API
IdentifierServiceIdentifier registration, validation, deduplicationKvK API (for KvK numbers)
ContactServiceContact management, email validationNone
EndpointServiceEndpoint registration, health checksNone
TokenServiceVAD token generation, signature verificationNone (crypto / HSM-backed key)
graph LR
    subgraph "Database Repository (Panache)"
        A[ParticipantRepository]
        B[LegalEntityRepository]
        C[IdentifierRepository]
        D[ContactRepository]
        E[EndpointRepository]
    end

    A --> F[PostgreSQL Connection Pool]
    B --> F
    C --> F
    D --> F
    E --> F

    F --> G[PostgreSQL Database]

Repository Pattern:

  • Each repository handles CRUD for one entity
  • Parameterized queries (never string concatenation)
  • Transaction support for multi-table operations
  • Connection pooling (max 20 connections)

Example: ParticipantRepository (Panache)

@ApplicationScoped
public class ParticipantRepository
implements PanacheRepositoryBase<Participant, UUID> {
public List<Participant> search(ParticipantFilters filters, Page page) { ... }
// findById, persist, update and delete are provided by Panache
}

KvK API Integration:

sequenceDiagram
    participant API
    participant KvKService
    participant KvKAPI

    API->>KvKService: verifyKvKNumber(kvkNumber)
    KvKService->>KvKAPI: GET /basisprofiel/{kvkNumber}
    KvKAPI-->>KvKService: Company Details
    KvKService->>KvKService: Map to LegalEntity
    KvKService-->>API: Verified Entity Data

Responsibilities:

  • Verify KvK numbers exist
  • Fetch company details (name, address, status)
  • Map KvK response to internal data model
  • Cache KvK responses (24-hour TTL)
  • Handle rate limiting (max 100 req/min)

BDI Framework Integration:

sequenceDiagram
    participant Client
    participant BDIService
    participant CryptoService
    participant Database

    Client->>BDIService: Generate VAD Token
    BDIService->>Database: Fetch Member Data
    Database-->>BDIService: Member Details
    BDIService->>BDIService: Build JWT Claims
    BDIService->>CryptoService: Sign with RSA Private Key
    CryptoService-->>BDIService: Signed Token
    BDIService-->>Client: VAD Token

Responsibilities:

  • Generate VAD tokens (JWT with custom claims)
  • Manage RSA key pairs (private key in Key Vault / HSM)
  • Implement BDI specification v2.0 compliance (VAD portion)

The ASR data model has its own document: see ASR Data Model for the full entity reference, the Keycloak ownership boundary (per ADR-00003), and the meta-field convention. A zoom-friendly, ERD-only companion is at ctn-asr-data-model-erd.md.

The high-level view below shows the core entities and their links to Keycloak. Client credentials live in Keycloak; the ASR holds only a reference (no secret). Dashed lines are logical cross-system links (resolved by ID, no database FK).

erDiagram
    participant ||--o{ legal_entity : "has"
    legal_entity ||--o{ legal_entity_identifier : "has"
    legal_entity ||--o{ contact : "has"
    participant ||--o{ endpoint : "registers"
    participant ||--o{ verification : "verified by"
    participant ||--o{ participant_role : "plays"
    participant ||--o{ m2m_client_ref : "owns"
    participant |o--o{ onboarding_request : "results in"

    participant ||..|| kc_organization : "org-id link (no FK)"
    m2m_client_ref ||..|| kc_m2m_client : "client-id link (no FK)"

    participant {
        uuid participant_id PK
        varchar keycloak_organization_id UK
        varchar status
    }
    legal_entity {
        uuid legal_entity_id PK
        uuid participant_id FK
        varchar primary_legal_name
    }
    legal_entity_identifier {
        uuid identifier_id PK
        uuid legal_entity_id FK
        varchar identifier_type
        varchar validation_status
    }
    contact {
        uuid contact_id PK
        uuid legal_entity_id FK
        varchar contact_type
    }
    endpoint {
        uuid endpoint_id PK
        uuid participant_id FK
        varchar lifecycle_status
    }
    verification {
        uuid verification_id PK
        uuid participant_id FK
        varchar type
        varchar status
    }
    participant_role {
        uuid role_id PK
        uuid participant_id FK
        varchar role
    }
    m2m_client_ref {
        uuid m2m_client_ref_id PK
        uuid participant_id FK
        varchar keycloak_client_id UK
    }
    onboarding_request {
        uuid request_id PK
        uuid participant_id FK
        varchar status
    }
    kc_organization {
        varchar org_id PK
        varchar alias
    }
    kc_m2m_client {
        varchar client_id PK
        varchar secret "in Keycloak only"
    }

Every ASR table carries the same meta fields — created_at, updated_at, created_by, updated_by, is_deleted — and UUID primary keys. Full per-entity detail, including the audit_record log and the Keycloak user / organization-group entities, is in the ASR Data Model document.

graph TB
    subgraph "Azure West Europe"
        subgraph "Static Web Apps"
            A[Admin Portal]
            B[Participant Portal]
        end

        subgraph "Kubernetes (AKS)"
            C[ASR API Quarkus]
        end

        subgraph "Database"
            D[PostgreSQL Flexible Server]
        end

        subgraph "Security"
            E[Keycloak]
            F[Azure Key Vault]
        end
    end

    A --> C
    B --> C
    C --> D

    A -.->|Auth| E
    B -.->|Auth| E
    C -.->|Secrets| F

    G[Internet Users] --> A
    G --> B

The Association Register data model and ERD are maintained as Mermaid diagrams in ASR Data Model and the ERD-only view. Trust-list internals are described in Trust Lists Architecture.

There is no central policy decision component in the 2026 architecture. Each data provider:

  1. Validates the consumer’s VAD against the ASR’s JWKS endpoint.
  2. Applies its own local policy (participant status, jurisdiction, data product, requester tier).
  3. Returns the data shaped by that policy, or denies the request.

The BDI Connector is a reference implementation that providers can adopt to get VAD validation and policy hooks out of the box.

  • A lean 2026 specification for the Visibility Achterland Containerlogistiek portal will be added here. The earlier draft (which assumed federated Orchestration Registers and an OPA PDP) is parked under docs/_out-of-scope-2026/.
graph TB
    subgraph "Presentation Layer"
        AdminPortal[Admin Portal]
        MemberPortal[Participant Portal]
        DataConsumerPortal[Data-Consumer Portal<br/>Visibility]
    end

    subgraph "Business Logic Layer"
        AssocReg[Association Register]
        VADGen[VAD Generator]
        DNS[DNS Validator]
        Enrich[Identifier Enrichment<br/>KvK / KBO / VIES / GLEIF]
        EndpointReg[Endpoint Registry & Discovery]
    end

    subgraph "Data Layer"
        PG[(PostgreSQL)]
    end

    subgraph "Infrastructure Layer"
        KeyVault[Secret Store / HSM-backed keys]
        IdP[OAuth / OIDC Server<br/>Keycloak]
    end

    AdminPortal --> AssocReg
    MemberPortal --> AssocReg
    DataConsumerPortal --> AssocReg
    AssocReg --> VADGen
    AssocReg --> DNS
    AssocReg --> Enrich
    AssocReg --> EndpointReg
    AssocReg --> PG
    VADGen --> KeyVault
    AssocReg --> IdP
  1. The participant’s authorised representative authenticates via eHerkenning.
  2. The ASR runs the enrichment + verification checks (KvK / KBO / VIES / GLEIF).
  3. On success the IdP issues an OAuth client credential.
  4. When the consumer requests a VAD via the OAuth client-credentials grant, the ASR signs and returns the VAD (JWS).
sequenceDiagram
    participant Consumer
    participant ASR as Association Register
    participant Provider as Data Provider

    Consumer->>ASR: OAuth client-credentials grant
    ASR-->>Consumer: VAD (JWS-signed JWT)
    Consumer->>Provider: GET /data-product (Bearer VAD)
    Provider->>ASR: GET /.well-known/jwks (cached)
    ASR-->>Provider: JWKS
    Provider->>Provider: Validate signature, claims, expiry
    Provider->>Provider: Apply local policy
    alt Authorised
        Provider-->>Consumer: 200 OK + data
    else Denied
        Provider-->>Consumer: 403 Forbidden
    end
  • REST APIs: OpenAPI 3.x specifications.
  • Tokens: JWS-signed JWTs (RS256).
  • Health: standard health-check endpoints.
  • Discovery: ASR exposes a discovery API listing registered endpoints and their OpenAPI specs.
  • Idempotency: safe retries on POST.
  • Circuit breaker: resilience for external calls (KvK, KBO, VIES, GLEIF).
  • Audit logging: structured JSON for every state change and authorisation decision.
graph LR
    subgraph "External Dependencies"
        IDP[OAuth / OIDC IdP]
        KVK[Chamber of Commerce]
        KBO[KBO Belgium]
        GLEIF[LEI Registry]
        VIES[VIES VAT]
        EH[eHerkenning]
    end

    subgraph "CTN Components"
        AssocReg[Association Register]
    end

    AssocReg --> KVK
    AssocReg --> KBO
    AssocReg --> GLEIF
    AssocReg --> VIES
    AssocReg --> IDP
    AssocReg --> EH
association:
verification:
kvk_enabled: true
kbo_enabled: true
vies_enabled: true
gleif_enabled: true
dns_enabled: true
token:
signing_algorithm: RS256
validity_hours: 24
re_verification:
interval_months: 12 # at least annually
high_risk_interval_months: 3 # quarterly where risk requires
ComponentMin InstancesMax InstancesScaling Metric
Association Register210CPU > 70%
Discovery API210Request rate
IdP (Keycloak)28CPU + active sessions
  • Database: General Purpose tier with read replicas.
  • Kubernetes pods (AKS): right-sized requests/limits with autoscaling.
  • Storage: hot tier for active documents.
  • Managed Identity: service-to-service authentication.
  • Private Endpoints: network isolation for databases.
  • Key Vault Integration: HSM-backed secret management for signing keys.
  • TLS Everywhere: encrypted communication, TLS 1.3.
  • Private Key Storage: HSM-backed keys in Key Vault
  • Certificate Rotation: Automated 90-day rotation
  • Token Expiration: Time-limited validity
  • Signature Validation: JWKS endpoint verification

For implementation details, refer to:

  1. Individual component documentation (linked above)
  2. Runtime scenarios for dynamic behavior
  3. Deployment view for infrastructure
  4. Cross-cutting concepts for patterns

← Back to Solution Strategy | ↑ Back to Index | Next: Runtime View →

In samenwerking met

Connected Trade NetworkConclusionData in LogisticsContargoInland Terminals GroupVan Berkel