IIdentityProvider.Resolve returned a non-nullable CallerIdentity, so
the interface could not express "no identity" - StubIdentityProvider
was forced to invent one for any request carrying no credential at
all. Consequence: a production behandelportal build sends no
X-Medewerker header (medewerkerInterceptor is dev-only), so it used
to authenticate as the seeded citizen, role drafter - failing closed
on backoffice capabilities but open on every citizen-scoped endpoint,
including CanRevealBigNummer.
Resolve now returns CallerIdentity?. StubIdentityProvider keeps a
non-nullable return type (a valid narrower override) since it never
itself has "no identity" to report - it is registered only under
IsDevelopment() now. Production registers nothing and throws an
InvalidOperationException immediately during startup instead: there
is no real DigiD/employee-SSO provider in this POC yet, so a
misconfigured Production deploy must fail before serving a single
request, not resolve one per request. The identity-resolution
middleware turns a null resolution into a 401 rather than passing it
downstream.
Added StubIdentityProviderTests.Never_returns_null_even_with_no_headers_at_all
and ProductionIdentityProviderTests, which builds its own
WebApplicationFactory<Program> with UseEnvironment("Production") and
asserts startup throws. Verified both new tests fail red against the
pre-fix code.
RB-01's residual (GET /uploads/{id}/content reached via plain browser
navigation, no identity header) is confirmed unchanged in Development
and its Production consequence is written up in
implementation/rb-09.md for whoever lands the real identity provider -
no signed-URL/cookie scheme was designed here, per scope.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BIG-register BFF (ASP.NET Core)
The backend that hosts the business rules for the BIG-register portal. The
frontend renders the decisions this service computes; it does not recompute them
(BFF-lite + decision DTOs — see ../docs/reference/architecture/0001-bff-lite-decision-dtos.md).
No real BRP/DUO: the reference data they'd return (registration, person, diplomas,
notes — Data/SeedData.cs) is in-memory and seeded, but the endpoints, DTOs,
status codes and error envelope are production-shaped.
Applications, documents and the brief persist to a SQLite file
(src/BigRegister.Api/bigregister.db, EF Core-backed — Data/AppDbContext.cs,
Data/Db.cs) created and migrated on first run; restarting the process (or
docker compose restart api — the existing ./backend:/src bind mount already
covers it, see docker-compose.yml) does not lose data. Delete the file to
reset demo data back to empty, the same state a fresh clone starts from. This is
a deliberate, right-sized choice for a POC (SQLite, no external DB service) — see
docs/project/backlog/WP-22-durable-persistence.md.
Run
Everything (docker-compose, from repo root)
docker compose up
- App: http://localhost:4200
- Swagger UI: http://localhost:5000/swagger
Backend only (local)
cd backend
dotnet run --project src/BigRegister.Api
# → http://localhost:5000/swagger
Frontend against a local backend
npm start # ng serve, proxies /api → http://localhost:5000 (proxy.conf.json)
Tests
cd backend && dotnet test # rule unit tests + endpoint integration tests
API
| Method | Route | Purpose |
|---|---|---|
| GET | /api/dashboard-view |
registration + person + computed herregistratie decision |
| GET | /api/notes |
specialisms / aantekeningen |
| GET | /api/brp/address |
BRP address lookup (gevonden:false = no address) |
| GET | /api/duo/diplomas |
diplomas with derived profession + applicable policy questions, + manual fallback |
| GET | /api/intake/policy |
scholing threshold (config value) |
| POST | /api/registrations |
submit registration → reference, or 422 (manual diploma) |
Rejections use ProblemDetails (RFC 7807) with status 422. Every request
carries an X-Correlation-Id (set by the FE fetch adapter); the backend echoes it
into a no-PII submit-audit log line (kind, outcome, reference, correlation id)
— the seam for real structured logging / an audit store.
Versioning
Endpoints live under /api/v1. Additive changes (a new optional field) stay on
v1: the NSwag-generated client and the FE parse* boundary ignore unknown fields,
so old clients keep working. A breaking change (renamed/removed field, changed
semantics) is introduced as /api/v2 served alongside v1 until clients migrate.
Where the rules live (src/BigRegister.Api/Domain/)
Diplomas/DiplomaRules.cs— profession derivation + which policy questions apply.Registrations/HerregistratieRule.cs— eligibility + reason + status invariant.Intake/IntakePolicy.cs— scholing threshold + completeness re-validation on submit (RejectIncompleteScholing, WP-69).Submissions/SubmissionRules.cs— submit rejections + reference generation.
Typed client (NSwag)
The frontend calls this API through a generated TypeScript client. Regenerate it from the contract after a shape change:
npm run gen:api # builds backend → swagger.json → src/app/shared/infrastructure/api-client.ts
Maintainability: changing a policy is one backend change
Goal: require every Verpleegkundige diploma to confirm a Dutch skills assessment. This is a new policy question on a diploma type.
Edit one file — Domain/Diplomas/DiplomaRules.cs:
public static IReadOnlyList<PolicyQuestion> QuestionsFor(Diploma d)
{
var questions = new List<PolicyQuestion>();
if (d.Engelstalig)
questions.Add(NlTaalEngelstalig);
+ if (d.Opleiding == "verpleegkunde")
+ questions.Add(new PolicyQuestion(
+ "bekwaamheid",
+ "Heeft u in de afgelopen vijf jaar een bekwaamheidstoets afgelegd?",
+ QuestionType.JaNee));
return questions;
}
Rebuild the backend (docker compose up or dotnet run). The new question now
appears in the registration wizard for HBO-Verpleegkunde.
- No frontend change. The FE renders whatever questions the API returns.
- No client regeneration. The wire shape (
PolicyQuestionDto) is unchanged — only the data behind it.npm run gen:apiis only needed when a DTO shape changes.
Add a unit test for the new rule in tests/BigRegister.Tests/RuleTests.cs and
you're done.