Cross-Cutting Concepts
Technical concepts and patterns applied across the Connected Trade Network
Security Concepts
Section titled “Security Concepts”Authentication and Authorization
Section titled “Authentication and Authorization”Authentication Methods (per ADR-00002 and ADR-00008):
Machine-to-Machine (M2M):├── Keycloak OAuth 2.0 Client Credentials│ ├── Self-hosted IdP (the chosen one)│ ├── RS256 JWT signing│ ├── JWKS-based validation│ └── Participant-scoped authorisation (Keycloak Organizations)└── mTLS (optional high security)
User Authentication (single IdP, outward federation per participant):├── Keycloak realm `ctn`│ ├── Federation → Entra ID (per participant, OIDC)│ ├── Federation → eHerkenning (SAML→OIDC, for NL admin-representation)│ └── Local Keycloak account (email + password + MFA) for participants without an own IdP└── MFA enforced on admin surfacesAuthorisation Pattern (2026) — VAD-only:
Request Authorisation Flow:1. Validate VAD token (participation proof) against ASR JWKS2. Apply provider's local policy3. Make authorisation decision4. Audit the decision
HTTP Headers:Authorization: Bearer <VAD-token>X-Correlation-Id: <request-id>Keycloak password policy (local accounts). Local Keycloak accounts enforce a password policy aligned with NIST SP 800-63B: minimum length 12, at least one each of upper/lower/digit/special, password history 10, max length 128 (DoS guard), PBKDF2-SHA256 ≥210 000 iterations, and no periodic forced expiry (change only on suspected breach). Federated users (Entra ID, eHerkenning) are governed by their own IdP’s policy.
Token Design Patterns
Section titled “Token Design Patterns”VAD Token Structure:
{ "iss": "https://association.ctn.network", "sub": "org-client-id", "aud": "https://ctn.network", "iat": 1704063600, "exp": 1704150000, "jti": "unique-token-id", "https://schemas.ctn.network/claims/legal_entity/name": "Transport Corp", "https://schemas.ctn.network/claims/legal_entity/kvk": "12345678", "https://schemas.ctn.network/claims/compliance/verified": true, "https://schemas.ctn.network/claims/participation/status": "active", "https://schemas.ctn.network/claims/identity_assurance_level": "substantial", "https://schemas.ctn.network/claims/participation/valid_until": "2026-12-31"}M2M Token Structure (Keycloak):
{ "iss": "https://keycloak.ctn.network/realms/ctn", "sub": "service-account-acme-tms-m2m", "aud": ["ctn-api"], "exp": 1730906400, "iat": 1730902800, "azp": "acme-tms-m2m", "client_id": "acme-tms-m2m", "scope": "openid api.access containers.read", "organization": { "acme-bv": { "id": "11111111-2222-3333-4444-555555555555" } }, "realm_access": { "roles": ["participant-member"] }}M2M Authentication Flow (full detail in Keycloak M2M Authentication):
1. M2M Client → Keycloak: POST /realms/ctn/protocol/openid-connect/token - grant_type=client_credentials - client_id + client_secret (Basic Auth) — or client_assertion JWT
2. Keycloak → M2M Client: JWT access token (RS256, ~1h TTL)
3. M2M Client → API: HTTP request, Authorization: Bearer <JWT>
4. API (quarkus-oidc) → Keycloak: Retrieve JWKS (cached ~10 minutes)
5. API: Validate JWT signature, iss, aud, exp, nbf, scopes
6. API → ASR DB: Resolve participant_id from token's organization claim
7. API: Apply participant-scoped policy, serve the requestEncryption Standards
Section titled “Encryption Standards”Data Protection:
| Context | Method | Key Management |
|---|---|---|
| Data at Rest | AES-256-GCM | Azure Key Vault |
| Data in Transit | TLS 1.3 | Managed certificates |
| Token Signing | RS256 (RSA + SHA-256) | HSM-backed keys |
| Sensitive Fields | Field-level encryption | Application keys |
| Backups | Azure Storage encryption | Microsoft-managed |
Development Concepts
Section titled “Development Concepts”API Design Standards
Section titled “API Design Standards”RESTful Principles:
URL Structure: Pattern: /{version}/{resource}/{id}/{sub-resource} Example: /v1/participants/12345/endpoints
HTTP Methods: GET: Retrieve resource(s) POST: Create new resource PUT: Full update (replace) PATCH: Partial update DELETE: Remove resource
Status Codes: 200: Success 201: Created 204: No Content 400: Bad Request 401: Unauthorized 403: Forbidden 404: Not Found 409: Conflict 429: Too Many Requests 500: Internal Server ErrorREST Maturity - Level 3 (HATEOAS):
CTN implements REST Level 3, using hypermedia controls to guide API consumers through available operations. This means API responses include _links that tell clients what actions are possible based on the current resource state.
📖 A separate REST Level 3 / HATEOAS explainer is planned; until then the Quick Example below is the canonical reference.
Quick Example:
{ "memberId": "ITG001", "status": "active", "_links": { "self": { "href": "/v1/participants/ITG001" }, "requestVadToken": { "href": "/v1/participants/ITG001/tokens/vad", "method": "POST" }, "updateProfile": { "href": "/v1/participants/ITG001", "method": "PATCH" } }}Benefits:
- Self-documenting APIs
- Workflow guidance through state-dependent links
- Loose coupling (clients follow links, don’t construct URLs)
- Graceful evolution without breaking clients
OpenAPI Specification:
openapi: 3.0.3info: title: CTN Association Register API version: 1.0.0paths: /v1/participants/{participantId}/endpoints: post: summary: Register a new data endpoint for a participant parameters: - name: participantId in: path required: true schema: { type: string, format: uuid } requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RegisterEndpointRequest' responses: 201: description: Endpoint registered headers: Location: schema: { type: string } description: URI of the created endpointRate Limiting
Section titled “Rate Limiting”Rate limiting is applied in two layers. The edge layer (Azure Front Door / WAF, see Front Door with WAF) absorbs volumetric abuse before it reaches the API. The application layer enforces finer, purpose-specific limits per caller.
Application-layer keys: authenticated requests are limited per caller identity (participant/user), anonymous requests per source IP. Limits are tiered by endpoint sensitivity:
| Tier | Limit (reference) | Applies to |
|---|---|---|
| API | 100 req / min | General read/write endpoints |
| Auth | 10 req / min | Interactive authentication endpoints |
| Token | 5 req / hour | Token issuance |
| Failed auth | 5 attempts / hour (per IP) | Repeated authentication failures |
| Upload | 20 req / hour | File-upload endpoints |
Failed authentication attempts carry an extra penalty so brute-force attempts exhaust their budget faster than the nominal limit suggests.
Responses advertise the budget via standard headers (IETF RateLimit headers): RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset. On exceedance the API returns 429 with Retry-After and an RFC 9457 problem body:
{ "type": "https://schemas.ctn.network/problems/rate-limit-exceeded", "title": "Too Many Requests", "status": 429, "detail": "Rate limit exceeded; retry after 60 seconds", "retry_after": 60}Consumers must honour Retry-After and back off exponentially. Limits are configurable per environment (relaxed in non-production). In-process counters do not share state across instances, so a multi-instance deployment uses a shared backing store (e.g. Redis) or delegates distributed limiting to the API gateway.
Error Handling
Section titled “Error Handling”Standard Error Response:
{ "error": { "code": "CTN_AUTH_002", "message": "VAD token validation failed", "details": { "reason": "Token expired", "expired_at": "2026-05-29T10:00:00Z" }, "timestamp": "2026-05-29T11:00:00Z", "correlation_id": "req-abc-123", "documentation": "https://docs.ctn.network/errors/CTN_AUTH_002" }}Error Categories:
| Code Range | Category | Examples |
|---|---|---|
| CTN_VAL_* | Validation | Invalid input, missing fields |
| CTN_AUTH_* | Authentication | Invalid token, expired |
| CTN_AUTHZ_* | Authorization | Insufficient permissions |
| CTN_BUS_* | Business Logic | Invalid state, rule violation |
| CTN_INT_* | Integration | External service failure |
| CTN_SYS_* | System | Internal error, resource unavailable |
Logging and Monitoring
Section titled “Logging and Monitoring”Structured Logging:
{ "timestamp": "2026-05-29T10:30:45.123Z", "level": "INFO", "correlation_id": "req-abc-123", "service": "association-register", "operation": "registerEndpoint", "duration_ms": 145, "user_id": "user-456", "participant_id": "PARTICIPANT-789", "endpoint_id": "ENDPOINT-012", "result": "SUCCESS", "metadata": { "participation_status": "active", "verification_level": "FULL" }}Logging Levels:
- ERROR: System errors, failures requiring attention
- WARN: Degraded performance, potential issues
- INFO: Normal operations, key business events
- DEBUG: Detailed diagnostic information
Sensitive data — never log. Logs are an exfiltration surface and fall under GDPR. The following must never appear in log output, in any field:
- Passwords and password hashes
- Full API tokens or JWTs (log at most a short prefix, e.g. first 8 chars +
…) - Client secrets and signing keys
- Full personal data (BSN/SSN, payment details, health data)
Safe to log: participant IDs and user UUIDs (not emails in security contexts), token prefixes, hashed values, IP addresses (for security monitoring) and user-agent strings. PII that must be correlated is masked or tokenised before logging.
Security-event logging. Authentication and authorisation outcomes are logged as distinct, queryable security events — not folded into generic request logs — so they can drive alerting:
- Authentication failure — reason (e.g. invalid signature, expired token), source IP, correlation ID. No token contents.
- Authorisation failure — subject, required vs. granted scopes/roles, the resource, correlation ID.
- Suspicious activity — repeated failures from one source, token reuse from multiple IPs, anomalous request rates.
{ "timestamp": "2026-05-29T10:31:02.500Z", "level": "WARN", "event": "security.authn.failure", "correlation_id": "req-abc-123", "reason": "invalid_token_signature", "ip": "203.0.113.7"}These events feed the audit pipeline and the alerting rules in the Deployment View.
Metrics Collection:
Business Metrics:├── participants.onboarded (counter)├── endpoints.registered (counter)├── vad.tokens.issued (counter)├── verifications.completed (counter)└── re_verifications.scheduled (counter)
Technical Metrics:├── api.response.time (histogram)├── database.query.time (histogram)├── cache.hit.ratio (gauge)├── external_api.latency (histogram, per provider: KvK / KBO / VIES / GLEIF)└── error.rate (counter)Architectural Patterns
Section titled “Architectural Patterns”Caching Strategy
Section titled “Caching Strategy”Multi-Level Cache:
Level 1: CDN Cache├── Static content (Portal assets)├── Public API responses└── TTL: 24 hours
Level 2: Consumer-side JWKS cache├── Public keys of the ASR signing certificate├── Refreshed on key-rotation events└── TTL: 10 minutes
Level 3: Application Cache (in-process)├── KvK / KBO / VIES / GLEIF lookup results├── Reference data└── TTL: 5 minutes (lookups), 24 hours (reference)Cache Invalidation:
- Event-driven invalidation
- TTL-based expiration
- Manual purge capabilities
- Cache-aside pattern for consistency
Event-Driven Patterns
Section titled “Event-Driven Patterns”Event Schema (CloudEvents):
{ "specversion": "1.0", "type": "org.ctn.participant.verified", "source": "/association-register", "id": "evt-123", "time": "2026-05-29T10:30:00Z", "datacontenttype": "application/json", "data": { "participant_id": "PARTICIPANT-789", "verification_source": "KvK", "verified_at": "2026-05-29T10:29:55Z" }}Event Topics:
Participation Events:├── participant.registered├── participant.verified├── participant.re_verification_due├── participant.suspended└── participant.offboarded
Endpoint Events:├── endpoint.registered├── endpoint.suspended└── endpoint.retired
Authorisation Events (at data provider, optional):├── access.granted└── access.deniedResilience Patterns
Section titled “Resilience Patterns”Circuit Breaker (Quarkus / SmallRye Fault Tolerance):
@CircuitBreaker( requestVolumeThreshold = 10, failureRatio = 0.5, delay = 30, delayUnit = ChronoUnit.SECONDS, successThreshold = 5)public Uni<KvkProfile> verifyKvk(String kvkNumber) { return kvkClient.basisprofiel(kvkNumber);}Retry Policy:
Retry Configuration: MaxAttempts: 3 BackoffStrategy: Exponential InitialDelay: 1s MaxDelay: 30s RetryableErrors: - NetworkError - TimeoutError - ServiceUnavailableData Management
Section titled “Data Management”Data Governance
Section titled “Data Governance”Data Classification:
| Level | Description | Examples | Protection |
|---|---|---|---|
| Public | Open information | API docs, schemas | None required |
| Internal | Business operations | Participant records, endpoint metadata | Access control |
| Confidential | Sensitive business | Pricing, contracts | Encryption + audit |
| Restricted | Regulated data | Personal data, UBO | Full protection |
Data Retention:
Retention Policies:├── Operational Data: 90 days active + 1 year archive├── Audit Logs: 7 years (regulatory requirement)├── Tokens: Duration of validity + 30 days├── Personal Data: As per GDPR requirements└── Backups: 30 days rolling windowData Quality
Section titled “Data Quality”Validation Rules:
- Input validation at API gateway
- Schema validation for events
- Business rule validation in services
- Referential integrity in database
Data Consistency:
- ACID transactions for critical operations
- Eventual consistency for distributed data
- Idempotency keys for duplicate prevention
- Compensating transactions for rollbacks
Performance Optimization
Section titled “Performance Optimization”Database Optimization
Section titled “Database Optimization”-- Indexes for common queriesCREATE INDEX idx_participant_status ON participant(kvk, status) WHERE status = 'ACTIVE';CREATE INDEX idx_endpoint_participant ON endpoint(participant_id);CREATE INDEX idx_identifier_lookup ON legal_entity_identifier(identifier_type, identifier_value);
-- Partitioning for large tables (yearly)CREATE TABLE audit_log_2026 PARTITION OF audit_logFOR VALUES FROM ('2026-01-01') TO ('2027-01-01');API Performance
Section titled “API Performance”- Response compression (gzip)
- Pagination for large datasets
- Field filtering (?fields=id,name)
- ETag support for caching
- Connection pooling
Compliance and Audit
Section titled “Compliance and Audit”Audit Requirements
Section titled “Audit Requirements”{ "audit_record": { "id": "audit-123", "timestamp": "2026-05-29T10:30:00Z", "actor": { "id": "user-456", "type": "SERVICE_ACCOUNT", "ip_address": "10.0.2.15" }, "action": "DATA_ACCESS", "resource": { "type": "CONTAINER_ETA", "id": "CNT-ABC-123" }, "authorisation": { "vad_token_id": "token-789", "decision": "ALLOW" }, "metadata": { "correlation_id": "req-abc-123", "session_id": "sess-def-456" } }}GDPR Compliance
Section titled “GDPR Compliance”- Right to access (data export)
- Right to rectification (data updates)
- Right to erasure (data deletion)
- Data portability (standard formats)
- Privacy by design principles
These cross-cutting concepts ensure consistency and quality across all CTN components.
In samenwerking met




