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

2.7 KiB

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.