Athena — europeLogin/compliance/processes/data-flow.md

Process Documentation

Process: Authentication Data Flow — Company Representative Authentication Project: Europe Login — Dutch Business Identity & Authentication Platform Owner: GloryLabs Date: 2026-03-27


1. Purpose

This document describes how personal data flows through the Europe Login system during the primary authentication flow: a company representative authenticating on behalf of their company to obtain a JWT token at a specified assurance level (BASIS, MIDDEN, or HOOG). It covers data entry points, processing steps, storage locations, third-party interactions, and deletion.


2. Scope

Systems involved:

  • Angular 20 frontend (Europe Login Portal, port 4301 dev / HTTPS in production)
  • Spring Boot 3.3.5 backend API (port 8091 dev / HTTPS in production)
  • PostgreSQL database (port 5434 dev) — persistent storage of representatives and company data
  • Redis (port 6380 dev) — session cache and token revocation list
  • KvK API (external, Dutch Chamber of Commerce) — company registration validation

Data involved:

  • BSN (Burger Service Nummer) — AES-encrypted at rest, never persisted in plaintext, never included in JWT or API responses
  • KvK number — company identifier, 8-digit format
  • Representative role — BESTUURDER, GEMACHTIGDE, or GEVOLMACHTIGDE
  • Assurance level — BASIS, MIDDEN, or HOOG
  • JWT token — issued to the relying party after successful authentication
  • IP address — logged for audit purposes

People involved:

  • Company representative (data subject authenticating)
  • Company administrator (manages representative records)
  • Relying party (service provider consuming the JWT)

3. Trigger

The authentication data flow is triggered when a company representative submits their credentials (e-mail address, password, and optionally a second factor depending on assurance level) via the Europe Login Portal to authenticate on behalf of their company.

A secondary trigger is the registration flow: a company administrator adding or updating a representative record, which triggers BSN ingestion and encryption.


4. Process Steps

4A — Representative Registration (data ingestion)

| Step | Actor | Action | System | Output | |------|-------|--------|--------|--------| | 1 | Company administrator | Submits representative details (BSN, name, email, role) via portal | Angular frontend | HTTP POST to backend API (TLS) | | 2 | Backend API | Validates input; checks KvK number format (8 digits) via KvkValidationService | Spring Boot backend | Validation result | | 3 | Backend API | Calls KvK API with KvK number to confirm company is active | KvK API (external, HTTPS) | Company status (active / inactive) | | 4 | Backend API | Rejects request if company is inactive | Spring Boot backend | HTTP 400 / error response to frontend | | 5 | Backend API | Encrypts BSN using AES with bsn-encryption-key (from Kubernetes Secret / env var) | Spring Boot backend (in-memory) | AES ciphertext of BSN | | 6 | Backend API | Persists representative record (encrypted BSN, name, email, role, KvK number) to PostgreSQL | PostgreSQL | Representative record stored | | 7 | Backend API | Returns success response (no BSN in response) | Spring Boot backend | HTTP 201; BSN not included in response |

4B — Authentication Flow (token issuance)

| Step | Actor | Action | System | Output | |------|-------|--------|--------|--------| | 1 | Company representative | Submits email, password (and 2FA if MIDDEN/HOOG) via login form | Angular frontend | HTTP POST to /auth/login (TLS) | | 2 | Backend API | Validates credentials against PostgreSQL representative record | Spring Boot + PostgreSQL | Authentication result (pass / fail) | | 3 | Backend API | Calls KvK API with KvK number to confirm company is still active | KvK API (external, HTTPS) | Company status | | 4 | Backend API | Rejects authentication if company is inactive | Spring Boot backend | HTTP 401 / error response | | 5 | Backend API | Decrypts BSN from PostgreSQL using AES key (in-memory only; never persisted or logged in plaintext) | Spring Boot backend (in-memory) | Plaintext BSN in memory | | 6 | Backend API | Evaluates assurance level requirements; verifies 2FA token if MIDDEN or HOOG | Spring Boot backend | Assurance level confirmed | | 7 | Backend API | Issues signed JWT containing: subject (user identifier), KvK number, role, assurance level, expiry; BSN is NOT included in JWT | Spring Boot backend | Signed JWT token | | 8 | Backend API | Stores session metadata (session ID, token expiry, revocation flag) in Redis with TTL | Redis | Session entry (max 24h TTL) | | 9 | Backend API | Logs authentication event (timestamp, KvK number, assurance level, IP, result) — no BSN in log | Application log / audit log | Audit entry | | 10 | Backend API | Returns JWT to frontend | Spring Boot backend | HTTP 200 + JWT | | 11 | Angular frontend | Passes JWT to relying party / stores in secure context | Browser / relying party | Authenticated session |

4C — Token Validation (by relying party)

| Step | Actor | Action | System | Output | |------|-------|--------|--------|--------| | 1 | Relying party | Presents JWT to Europe Login validation endpoint (or validates locally using public key) | Relying party + Spring Boot backend | JWT validation request | | 2 | Backend API | Verifies JWT signature, expiry, and checks Redis revocation list | Spring Boot + Redis | Valid / revoked / expired | | 3 | Backend API | Returns validation result and decoded claims (KvK number, role, assurance level) | Spring Boot backend | Claims or rejection |

4D — Session Termination / Logout

| Step | Actor | Action | System | Output | |------|-------|--------|--------|--------| | 1 | Company representative or system | Initiates logout or token expiry reached | Angular frontend / timer | Logout request | | 2 | Backend API | Adds JWT ID to Redis revocation list | Redis | Token marked as revoked | | 3 | Redis | Session entry expires automatically via TTL (max 24h) | Redis | Session data deleted |


5. Data Flow

[Company Representative]
        │
        │  email + password (+ 2FA)
        ▼
[Angular Frontend] ──── HTTPS/TLS ────► [Spring Boot Backend API]
                                                │
                         ┌──────────────────────┤
                         │                      │
                         ▼                      ▼
              [PostgreSQL]               [KvK API (external)]
              - encrypted BSN            - company status lookup
              - representative data      - KvK number only sent
              - KvK number               - no personal data sent
              - role
                         │
                         ▼
              [AES decryption (in-memory)]
              BSN plaintext exists only
              transiently in JVM heap
              — never logged, never in JWT
                         │
                         ▼
              [JWT issued]
              Contains: subject, KvK, role,
              assurance level, expiry
              Does NOT contain: BSN, name
                         │
              ┌──────────┴──────────┐
              ▼                     ▼
          [Redis]           [Relying Party]
          session cache     receives JWT
          TTL 24h           validates claims
          revocation list

Data that leaves the system boundary:

  • KvK number → KvK API (no personal data)
  • JWT token → Relying party (subject ID, KvK number, role, assurance level — no BSN)

Data that never leaves the system boundary:

  • BSN (never in JWT, never in API response, never in logs)
  • Plaintext credentials

Storage locations (all in NL, TransIP VPS):

  • PostgreSQL: persistent representative data (BSN as AES ciphertext)
  • Redis: transient session cache (TTL-based, max 24h)
  • Application logs: authentication audit trail (no BSN, retained 90 days)

6. Error Handling

| Failure scenario | Behaviour | Data implications | |-----------------|-----------|-------------------| | Invalid KvK number format | Rejected at input validation; no KvK API call made | No data stored or transmitted | | KvK API returns inactive company | Authentication rejected with HTTP 401; authentication failure logged | No token issued; failure logged without BSN | | KvK API unavailable / timeout | Authentication rejected with HTTP 503; event logged | No token issued; system treats unavailability as a blocking error (no fallback to cached status) to prevent authentication with stale company data | | Invalid credentials | HTTP 401 returned; failed attempt logged with timestamp and IP | BSN decryption not attempted for failed credential check | | 2FA failure (MIDDEN/HOOG) | HTTP 401 returned; failed attempt logged | Token not issued; assurance level not downgraded silently | | BSN decryption failure (key unavailable) | HTTP 500; critical alert; authentication blocked | BSN plaintext never exposed; system fails closed | | Redis unavailable | Authentication proceeds but session not cached; token still valid (stateless JWT); revocation list unavailable — token cannot be revoked until Redis recovers | Degraded security posture; incident to be raised | | PostgreSQL unavailable | Authentication fails; HTTP 503 | No data exposure; system fails closed |


7. Related Documents

  • /Users/sarkout/projects/prive/ocs/europeLogin/dpia.md — Data Protection Impact Assessment
  • /Users/sarkout/projects/prive/ocs/europeLogin/verwerkingsregister.md — Art. 30 GDPR processing register
  • /Users/sarkout/projects/prive/ocs/europeLogin/nis2.md — NIS2 assessment
  • /Users/sarkout/projects/prive/ocs/europeLogin/bio.md — BIO assessment
  • /Users/sarkout/projects/prive/europeLogin/CLAUDE.md — Project rules, secrets management, domain rules

Reacties

Nog geen reacties