Files
httpauth-firebase/docs/adr/ADR-002-rbac-identity-output-contract.md
Rene Nochebuena d1de096c72 docs(httpauth-firebase): fix rbac tier reference from 1 to 0
rbac is a Tier 0 module (no micro-lib dependencies). The dependency line
incorrectly cited it as Tier 1. The module's own tier (4) is unchanged —
it remains the auth layer above the transport infrastructure.
2026-03-19 13:44:45 +00:00

56 lines
2.7 KiB
Markdown

# ADR-002: rbac.Identity as the Output Contract
**Status:** Accepted
**Date:** 2026-03-18
## Context
`AuthMiddleware` verifies a Firebase JWT and extracts a UID and claims map from the
decoded token. Downstream code needs a richer identity: application-specific role,
tenant, display name, email. Several design options exist:
1. Expose Firebase token fields (`auth.Token`) directly in context — ties all
downstream code to Firebase types.
2. Define a custom identity struct in `httpauth-firebase` — decouples from Firebase
but creates a module-specific contract that other `httpauth-*` modules cannot share.
3. Use `rbac.Identity` as the shared identity type — all `httpauth-*` modules
produce the same type; downstream code and `AuthzMiddleware` depend on `rbac`
only, not on any Firebase types.
## Decision
`EnrichmentMiddleware` calls `rbac.SetInContext(ctx, identity)` to store the
enriched identity. `AuthzMiddleware` reads it with `rbac.FromContext(ctx)`.
Downstream business logic and service handlers use `rbac.FromContext` directly —
they never import `httpauth-firebase`.
The flow is:
1. `AuthMiddleware` (this module): verifies Firebase JWT → stores `uid` + `claims`
in context under unexported keys local to this package.
2. `EnrichmentMiddleware` (this module): reads `uid` + `claims` → calls
`IdentityEnricher.Enrich` → stores `rbac.Identity` via `rbac.SetInContext`.
3. `AuthzMiddleware` (this module): reads `rbac.Identity` via `rbac.FromContext`
calls `PermissionProvider.ResolveMask` → allows or rejects.
The intermediate `uid` + `claims` context values are stored under unexported typed
keys (`ctxUIDKey{}`, `ctxClaimsKey{}`). They are internal to `httpauth-firebase`
and not part of the public API.
## Consequences
- Business logic and service layers only need to import `rbac` to read the caller's
identity. They have no knowledge of Firebase, JWTs, or token claims.
- Switching from Firebase to another provider (e.g. Auth0) requires replacing the
`AuthMiddleware` module. `EnrichmentMiddleware`, `AuthzMiddleware`, and all
downstream code remain unchanged because they operate on `rbac.Identity`.
- `IdentityEnricher` is the application's extension point: it receives the Firebase
UID and raw claims and returns a fully populated `rbac.Identity` with role and
tenant. This is the only place where app-specific user store queries should occur.
- `rbac.Identity.WithTenant(tenantID)` is called in `EnrichmentMiddleware` if a
tenant header is configured. The base identity from `Enrich` is immutable; a new
value is returned.
- Global ADR-003 (context helpers live with data owners) is applied here: `rbac`
owns `SetInContext` and `FromContext` because `rbac.Identity` is a RBAC concern,
not an auth transport concern.