# httpauth-firebase Firebase-backed HTTP middleware for authentication, identity enrichment, and role-based access control. ## Purpose `httpauth-firebase` provides three composable `net/http` middleware functions that implement the full auth stack: JWT verification via Firebase, app-specific identity enrichment via a caller-supplied `IdentityEnricher`, and permission enforcement via `rbac`. The output contract is always `rbac.Identity` — downstream code and business logic are decoupled from Firebase types entirely. ## Tier & Dependencies **Tier:** 4 (transport auth layer; depends on Tier 0 `rbac` and external Firebase SDK) **Module:** `code.nochebuena.dev/go/httpauth-firebase` **Direct imports:** `code.nochebuena.dev/go/rbac`, `firebase.google.com/go/v4/auth` `httpauth-firebase` does not import `logz`, `httpmw`, or `httputil`. It has no logger parameter — errors are returned as HTTP responses, not logged here. ## Key Design Decisions - **Provider-specific naming** (ADR-001): The module is named `httpauth-firebase` because it imports Firebase directly. Other providers live in sibling modules (`httpauth-auth0`, etc.). All converge on `rbac.Identity`. The `TokenVerifier` interface is retained for unit-test mockability only. - **rbac.Identity as output contract** (ADR-002): `EnrichmentMiddleware` stores the final identity with `rbac.SetInContext`; `AuthzMiddleware` reads it with `rbac.FromContext`. Downstream handlers only need to import `rbac`. Global ADR-003 applies: `rbac` owns its context helpers. - **Composable three-middleware stack** (ADR-003): `AuthMiddleware`, `EnrichmentMiddleware`, and `AuthzMiddleware` are separate functions. Each can be applied independently; each returns 401 if its upstream dependency is missing from context. ## Patterns **Full stack (most routes):** ```go r.Use(httpauth.AuthMiddleware(firebaseAuthClient, []string{"/health", "/metrics/*"})) r.Use(httpauth.EnrichmentMiddleware(userEnricher, httpauth.WithTenantHeader("X-Tenant-ID"))) // Per-route RBAC: r.With(httpauth.AuthzMiddleware(permProvider, "orders", rbac.Write)). Post("/orders", httputil.Handle(v, svc.CreateOrder)) ``` **Token-only (webhook verification):** ```go r.Use(httpauth.AuthMiddleware(firebaseAuthClient, nil)) // No enrichment, no authz — just verify the token is valid ``` **Reading identity in a handler:** ```go identity, ok := rbac.FromContext(r.Context()) if !ok { // should not happen if EnrichmentMiddleware is in the chain } fmt.Println(identity.UID, identity.Role, identity.TenantID) ``` **Public paths (bypass token check):** ```go // path.Match patterns — supports * wildcard publicPaths := []string{"/health", "/ready", "/metrics/*"} r.Use(httpauth.AuthMiddleware(firebaseAuthClient, publicPaths)) ``` **Interfaces the application must implement:** ```go // IdentityEnricher: called by EnrichmentMiddleware type MyEnricher struct{ db *sql.DB } func (e *MyEnricher) Enrich(ctx context.Context, uid string, claims map[string]any) (rbac.Identity, error) { user, err := e.db.LookupUser(ctx, uid) if err != nil { return rbac.Identity{}, err } return rbac.NewIdentity(uid, user.DisplayName, user.Email).WithRole(user.Role), nil } // PermissionProvider: called by AuthzMiddleware type MyPermProvider struct{} func (p *MyPermProvider) ResolveMask(ctx context.Context, uid, resource string) (rbac.PermissionMask, error) { return rbac.ReadWrite, nil } ``` ## What to Avoid - Do not use `AuthMiddleware` alone and assume the request is fully authorised. Token verification only confirms the token is valid; it does not enforce application-level roles or permissions. - Do not put `AuthzMiddleware` before `EnrichmentMiddleware` in the chain. `AuthzMiddleware` reads `rbac.Identity` from context; if enrichment has not run, it will return 401. - Do not read `firebase.Token` fields directly in business logic. The token UID and claims are stored under unexported context keys and are not part of the public API. Use `rbac.FromContext` to read the enriched identity. - Do not store application-specific data in Firebase custom claims and bypass the `IdentityEnricher`. Claims are read by `EnrichmentMiddleware` and passed to `Enrich`, but the enricher is the correct place to resolve application identity — not raw Firebase claims spread across handlers. - Do not import `httpauth-firebase` from service or domain layers. It is a transport package. Dependency should flow inward: handler → service, never service → handler/middleware. ## Testing Notes - `compliance_test.go` verifies at compile time that `*mockVerifier` satisfies `TokenVerifier`, `*mockEnricher` satisfies `IdentityEnricher`, and `*mockProvider` satisfies `PermissionProvider`. - `AuthMiddleware` can be tested by injecting a `mockVerifier` that returns a controlled `*auth.Token`. No real Firebase project is needed. - `EnrichmentMiddleware` can be tested by pre-populating the request context with `uid` via an upstream `AuthMiddleware` using a mock verifier. - `AuthzMiddleware` can be tested by pre-populating the request context with an `rbac.Identity` via `rbac.SetInContext` — no auth or enrichment middleware needed. - Use `httptest.NewRecorder()` and `httptest.NewRequest()` for all HTTP-level tests.