Athena — europeLogin/architecture.md

Architecture — Europe Login

Overview

Europe Login is a stateless JWT-based identity provider for Dutch companies. It mirrors the e-Herkenning assurance-level model but runs entirely on GloryLabs infrastructure.

┌─────────────────┐     HTTPS      ┌─────────────────────────────────────┐
│  Angular 20 SPA │ ◄────────────► │  Spring Boot 3  (port 8091)         │
│  port 4301      │                │  /api/auth   /api/onboarding        │
└─────────────────┘                │  /api/companies  /api/audit         │
                                   └──────────┬──────────────────────────┘
                                              │
                          ┌───────────────────┼───────────────────┐
                          │                   │                   │
                   ┌──────▼──────┐   ┌───────▼──────┐   ┌───────▼──────┐
                   │ PostgreSQL  │   │    Redis      │   │ GoCardless   │
                   │ port 5434   │   │  port 6380    │   │  PSD2 API    │
                   └─────────────┘   └──────────────┘   └──────────────┘

Domain model

Assurance levels

| Level | Meaning | How achieved | |-------|---------|--------------| | BASIS | KvK verified | KvK format check + KvK API | | MIDDEN | Bank account owner matches KvK name | BASIS + PSD2 name match | | HOOG | Full identity review | MIDDEN + manual document review (future) |

Core entities

  • Company — KvK number, name, assurance level, active flag
  • AuthorizedRepresentative — email, BCrypt password hash, AES-256-GCM encrypted BSN, role (BESTUURDER / GEMACHTIGDE / GEVOLMACHTIGDE), linked to Company
  • LoginSession — session token (UUID), company, rep, assurance level, expiry, revoked flag. Stored in DB + Redis.
  • OnboardingSession — multi-step wizard state machine, 24-hour TTL, KvK + PSD2 + rep identity fields
  • AuditEvent — hash-chained tamper-evident log (SHA-256), never stores BSN

Backend package layout

nl.glorylabs.europelogin
├── annotation/       @Auditable AOP annotation
├── aspect/           AuditAspect — intercepts @Auditable methods
├── config/           SecurityConfig, RedisConfig, OpenAPI config
├── controller/       AuthController, CompanyController,
│                     OnboardingController, AuditController
├── dto/              Request/response records (API boundary)
├── entity/           JPA entities + enums
├── exception/        Domain exceptions + GlobalExceptionHandler
├── repository/       Spring Data JPA repositories
├── security/         JwtAuthenticationFilter, JwtTokenProvider,
│                     RepresentativeUserDetails, RepresentativeUserDetailsService
└── service/          CompanyAuthService, TokenService, OnboardingService,
                      AuditService, KvkValidationService,
                      BsnEncryptionService, Psd2Service

Authentication flow

POST /api/auth/login
  1. validateFormat(kvkNumber)           ← KvkValidationService
  2. findByKvkNumberAndActiveTrue()      ← CompanyRepository
  3. meetsAssuranceLevel(actual, req)    ← ordinal comparison
  4. findByEmailAndActiveTrue(email)     ← AuthorizedRepresentativeRepository
  5. rep.company.id == company.id        ← ownership check
  6. passwordEncoder.matches(pw, hash)   ← BCrypt
  7. tokenService.issueToken(...)        ← JWT + LoginSession + Redis
  8. auditService.record(LOGIN_SUCCESS)  ← hash-chained audit event

Onboarding flow (4 steps)

POST /api/onboarding            → step 1: validate KvK, create OnboardingSession
POST /{id}/psd2/initiate        → step 2a: GoCardless requisition
GET  /{id}/psd2/complete?ref=   → step 2b: fetch accounts, name match
POST /{id}/representative       → step 3: capture BSN (encrypted), role
POST /{id}/complete             → step 4: create Company + AuthorizedRepresentative

Assurance levels awarded:

  • Steps 1+4 only → BASIS
  • PSD2 name matched → MIDDEN

Audit trail

Every @Auditable method is intercepted by AuditAspect. For auth events, CompanyAuthService and TokenService call auditService.record() directly to include full actor info (email, KvK, role).

Each AuditEvent stores:

  • previousEventHash — hash of the preceding event
  • eventHash — SHA-256 of timestamp|eventType|actorEmail|action|result|targetEntityId|previousEventHash

Chain verified via GET /api/audit/verify (BESTUURDER only).

BSN is never stored in audit events (AVG/GDPR compliance).

Security

  • Stateless JWT — no HTTP sessions
  • Session tokens (UUID) stored in DB + Redis for fast revocation
  • BCrypt (cost 12) for passwords
  • AES-256-GCM for BSN at rest (BsnEncryptionService)
  • @PreAuthorize("hasRole('BESTUURDER')") on audit endpoints
  • Spring Security returns 401 for unauthenticated (not 403) — configured via authenticationEntryPoint
  • AccessDeniedException mapped to 403 in GlobalExceptionHandler

Frontend

Angular 20 standalone components, no NgModules. Reactive forms with signals for state.

Key components:

  • LoginComponent — login form (KvK + email + password + assurance level)
  • OnboardingComponent — 5-step wizard shell (KvK → bank → representative → review → complete)
  • DashboardComponent — post-login landing
  • AuthService — JWT/session storage in sessionStorage, signal-based auth state
  • AuthGuardCanActivateFn redirecting unauthenticated users to /login
  • AuthInterceptor — injects Authorization: Bearer <token> on all API calls

Infrastructure

Ports (local)

| Service | Port | |---------|------| | Backend API | 8091 | | Frontend | 4301 | | PostgreSQL | 5434 | | Redis | 6380 |

Kubernetes namespaces

| Branch | Namespace | |--------|-----------| | main | europe-login | | develop | europe-login-staging |

CI/CD

push to feature/* → CI tests only
PR → develop      → CI: test + build + push ghcr.io → Helm upgrade staging
PR → main         → CI: test + build + push ghcr.io → Helm upgrade production

Workflows in .github/workflows/:

  • ci-backend.yml — Maven test + Docker build + push
  • ci-frontend.yml — npm test + ng lint + Selenium E2E + Docker build + push
  • e2e.yml — full-stack E2E with postgres + redis + selenium services
  • deploy-backend.yml / deploy-frontend.yml — Helm upgrade

Reacties

Nog geen reacties