ADR 0003: Enforce OIDC and remove the header fallback
- Status: accepted
- Date: 2026-11-07 (rollout completed)
- Decision makers: the user, who asked for real token enforcement
Context and problem statement
The services could validate Keycloak tokens, but production ran with OIDC off and a permit-all HTTP policy. They trusted X-User-Id and X-User-Role headers sent by the client. How should identity be established?
Decision drivers
- Identity from a header the client chooses is not identity.
- Switching enforcement on restores endpoints that had been failing closed.
- The services call each other, and those calls had to carry credentials first.
Considered options
- Keep header identity and add a gateway check.
- Enforce OIDC in every service and make the header identity an opt-in for local development.
Decision outcome
Enforce OIDC in all eight services. The authenticated HTTP policy covers /* and an enumerated list of public paths is the only exception. A verified token always wins over a header. The principal is preferred_username; roles come from realm roles. Header identity is an opt-in switch for local work, and a startup guard refuses to start a production service with it enabled. Service-to-service calls use client-credentials tokens.
The rollout was staged: realm import, then validation with the old permissive policy, then removal of the permit policy once service calls carried tokens.
Consequences
- Good: client-supplied identity headers no longer grant access. Admin endpoints return 401 without a token, 403 for the wrong role.
- Bad: Keycloak becomes a hard dependency of every protected path.
- Bad: rolling a service back to a pre-enforcement revision removes the protection for that service.
- Bad: login is not the same as ownership. Many endpoints do not check that a record belongs to the caller.
Confirmation
QUARKUS_OIDC_ENABLED=true on each Deployment. ServiceConfigContractTest and the enforcement harness in libs/common-models assert the configuration. A tokenless request to a protected path returns 401.
Sources
.agents/tasks/oidc-enforcement/finish/final-report.md, .kiro/steering/auth-rollout.md, libs/common-models/src/main/java/com/rtih/startupos/common/security/HeaderIdentityPolicy.java.