Skip to main content

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.