Files
httpauth-firebase/CLAUDE.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

5.2 KiB

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):

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):

r.Use(httpauth.AuthMiddleware(firebaseAuthClient, nil))
// No enrichment, no authz — just verify the token is valid

Reading identity in a handler:

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):

// path.Match patterns — supports * wildcard
publicPaths := []string{"/health", "/ready", "/metrics/*"}
r.Use(httpauth.AuthMiddleware(firebaseAuthClient, publicPaths))

Interfaces the application must implement:

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