feat(bff): BFF with one endpoint per portal + OIDC validation (closes #8) #64

Merged
not merged 7 commits from feat/8-bff into main 2026-07-01 09:32:45 +00:00
Contributor

What & why

S-07: the BFF (Backend-for-Frontend) — the single backend the portals talk to (§8.3). For the
walking skeleton it exposes:

  • POST /self-service/registrations — requires a valid Keycloak digid JWT; extracts the bsn
    claim and forwards it to the Domain Service (#6), returning 202. Missing/invalid/expired token → 401.
  • GET /openbaar/register?q=…anonymous public lookup (S-09) → reads the projection (#7) and
    returns only public-safe fields (id + status; the bsn never leaves the BFF).

Closes #8
Closes #63

Definition of Done

  • Linked Gitea issue (above).
  • Failing test committed before the implementation (red→green for endpoints + pure logic).
  • Conventional Commits referencing the issue (refs #8).
  • CI green — lint, build, unit + acceptance (80 tests), mutation, verify-stack (incl. verify-bff).
  • docker compose up reaches green health checks (bff health-checked; depends_on domain/projection/keycloak).
  • Docs — ADR-0010 + demo-script note.
  • ADR added — docs/architecture/adr-0010-bff-oidc.md.
  • Demo note in docs/demo-script.md.

Notes for reviewers

  • Auth is testable without infra: WebApplicationFactory tests reconfigure JWT bearer with a
    local test signing key, so valid/invalid/expired/wrong-key tokens are exercised in-process. A
    Reqnroll acceptance scenario drives the same over HTTP. Real Keycloak validation is the live-stack
    verify-bff check (mints a real digid token via direct grant against keycloak:8080).
  • Issuer consistency: Keycloak start-dev derives the issuer from the request host, so the BFF
    authority and the verify token request both use keycloak:8080 — no issuer mismatch (ADR-0010).
  • OpenAPI: services/bff/openapi.json is generated (typed 202/400/401 + 200 schemas; the
    volatile servers block is cleared for determinism) and guarded by a drift test — S-08's client is
    generated from it.
  • Mutation: 100% on the BFF's pure logic (OpenbaarProjection, break 90). Program.cs (wiring)
    and DownstreamClients.cs (HTTP adapters) are excluded — covered by the endpoint tests + verify-bff,
    consistent with how the other services scope Stryker.
  • Scope: behandelaar/beheer endpoints and downstream service-to-service auth are out of scope
    (later slices), per the issue and ADR-0010.

🤖 Generated with Claude Code

## What & why S-07: the **BFF (Backend-for-Frontend)** — the single backend the portals talk to (§8.3). For the walking skeleton it exposes: - `POST /self-service/registrations` — requires a valid Keycloak `digid` JWT; extracts the `bsn` claim and forwards it to the Domain Service (#6), returning 202. Missing/invalid/expired token → 401. - `GET /openbaar/register?q=…` — **anonymous** public lookup (S-09) → reads the projection (#7) and returns only public-safe fields (`id` + `status`; the bsn never leaves the BFF). Closes #8 Closes #63 ## Definition of Done - [x] Linked Gitea issue (above). - [x] Failing test committed before the implementation (red→green for endpoints + pure logic). - [x] Conventional Commits referencing the issue (`refs #8`). - [x] CI green — lint, build, unit + acceptance (80 tests), mutation, verify-stack (incl. verify-bff). - [x] `docker compose up` reaches green health checks (bff health-checked; depends_on domain/projection/keycloak). - [x] Docs — ADR-0010 + demo-script note. - [x] ADR added — `docs/architecture/adr-0010-bff-oidc.md`. - [x] Demo note in `docs/demo-script.md`. ## Notes for reviewers - **Auth is testable without infra:** `WebApplicationFactory` tests reconfigure JWT bearer with a local test signing key, so valid/invalid/expired/wrong-key tokens are exercised in-process. A Reqnroll acceptance scenario drives the same over HTTP. Real Keycloak validation is the live-stack **`verify-bff`** check (mints a real `digid` token via direct grant against `keycloak:8080`). - **Issuer consistency:** Keycloak `start-dev` derives the issuer from the request host, so the BFF authority and the verify token request both use `keycloak:8080` — no issuer mismatch (ADR-0010). - **OpenAPI:** `services/bff/openapi.json` is generated (typed 202/400/401 + 200 schemas; the volatile `servers` block is cleared for determinism) and guarded by a drift test — S-08's client is generated from it. - **Mutation:** 100% on the BFF's pure logic (`OpenbaarProjection`, break 90). `Program.cs` (wiring) and `DownstreamClients.cs` (HTTP adapters) are excluded — covered by the endpoint tests + verify-bff, consistent with how the other services scope Stryker. - **Scope:** behandelaar/beheer endpoints and downstream service-to-service auth are out of scope (later slices), per the issue and ADR-0010. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
not added 7 commits 2026-07-01 09:17:16 +00:00
The BFF is the portals' only backend (§8.3): it validates Keycloak digid-realm
JWTs on POST /self-service/registrations (extracting bsn → domain), leaves
GET /openbaar/register anonymous (public lookup, S-09), and fans out to the
domain and projection over typed HTTP clients. Tests mint tokens with a test
signing key; real Keycloak validation is a live-stack verify-bff check. Records
the container OIDC issuer-mismatch wrinkle. OpenAPI is generated + committed for
the S-08 client.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Failing tests for the BFF walking-skeleton endpoints:
- POST /self-service/registrations rejects missing/malformed/wrong-key/expired
  tokens (401) and, with a valid digid token, forwards the bsn to the domain and
  returns 202 (WebApplicationFactory + a local test signing key, ADR-0010).
- GET /openbaar/register serves public-safe rows anonymously (never the bsn) and
  filters by q.
- OpenbaarProjection.PublicView (pure) filters by id and maps to id+status only.

Endpoints and PublicView are stubs so the tests compile and fail on their
assertions; the green commit implements them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
POST /self-service/registrations requires a valid digid JWT, reads the bsn claim
and forwards it to the domain, returning 202. GET /openbaar/register is anonymous
and returns OpenbaarProjection.PublicView — rows filtered by q and mapped to the
public-safe id+status only (bsn/naam never exposed). JwtBearer validates
signature/issuer/expiry against the Keycloak digid authority (§8.3, ADR-0010).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Use-case-level BDD (Reqnroll) driving the real BFF over HTTP with fake downstreams
and locally-minted tokens: a valid DigiD token is accepted and the bsn forwarded
to the domain; a tokenless submit is 401; the openbaar register is anonymous and
never exposes the bsn (ADR-0010). Real Keycloak validation is the verify-bff check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document typed responses (202 SubmitAccepted / 400 / 401 on self-service; 200
OpenbaarEntry[] on openbaar) so the generated spec carries real schemas for S-08's
client. A document transformer clears the auto-populated servers block so the spec
is host-independent and deterministic. Commit services/bff/openapi.json and add a
test asserting it matches the served /openapi/v1.json (fails on drift).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the bff service in compose (Keycloak authority + downstream domain/projection
URLs, depends_on domain/projection healthy + keycloak started). run-bff-check.sh
verifies the BFF end-to-end against the up stack: 401 without a token, 202 with a
real digid token minted via direct grant against keycloak:8080 (host-consistent
issuer, ADR-0010), and an anonymous public-safe openbaar register (never a bsn).
Wired as verify-bff (Makefile + verify chain + CI step). Stryker baseline for the
BFF's pure logic (OpenbaarProjection) at 100% (break 90); Program/HTTP adapters are
covered by the endpoint tests + verify-bff. CI uploads the bff mutation report.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs(bff): demo note for the BFF front door (S-07) (refs #8)
All checks were successful
CI / lint (pull_request) Successful in 1m7s
CI / build (pull_request) Successful in 58s
CI / unit (pull_request) Successful in 1m2s
CI / mutation (pull_request) Successful in 3m47s
CI / verify-stack (pull_request) Successful in 5m56s
96d447832f
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
not added the type:slicearea:bff labels 2026-07-01 09:17:31 +00:00
not merged commit 5a4331a416 into main 2026-07-01 09:32:45 +00:00
Sign in to join this conversation.