# 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.