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.
56 lines
2.7 KiB
Markdown
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.
|