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.
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-firebasebecause it imports Firebase directly. Other providers live in sibling modules (httpauth-auth0, etc.). All converge onrbac.Identity. TheTokenVerifierinterface is retained for unit-test mockability only. - rbac.Identity as output contract (ADR-002):
EnrichmentMiddlewarestores the final identity withrbac.SetInContext;AuthzMiddlewarereads it withrbac.FromContext. Downstream handlers only need to importrbac. Global ADR-003 applies:rbacowns its context helpers. - Composable three-middleware stack (ADR-003):
AuthMiddleware,EnrichmentMiddleware, andAuthzMiddlewareare 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
AuthMiddlewarealone 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
AuthzMiddlewarebeforeEnrichmentMiddlewarein the chain.AuthzMiddlewarereadsrbac.Identityfrom context; if enrichment has not run, it will return 401. - Do not read
firebase.Tokenfields directly in business logic. The token UID and claims are stored under unexported context keys and are not part of the public API. Userbac.FromContextto read the enriched identity. - Do not store application-specific data in Firebase custom claims and bypass the
IdentityEnricher. Claims are read byEnrichmentMiddlewareand passed toEnrich, but the enricher is the correct place to resolve application identity — not raw Firebase claims spread across handlers. - Do not import
httpauth-firebasefrom service or domain layers. It is a transport package. Dependency should flow inward: handler → service, never service → handler/middleware.
Testing Notes
compliance_test.goverifies at compile time that*mockVerifiersatisfiesTokenVerifier,*mockEnrichersatisfiesIdentityEnricher, and*mockProvidersatisfiesPermissionProvider.AuthMiddlewarecan be tested by injecting amockVerifierthat returns a controlled*auth.Token. No real Firebase project is needed.EnrichmentMiddlewarecan be tested by pre-populating the request context withuidvia an upstreamAuthMiddlewareusing a mock verifier.AuthzMiddlewarecan be tested by pre-populating the request context with anrbac.Identityviarbac.SetInContext— no auth or enrichment middleware needed.- Use
httptest.NewRecorder()andhttptest.NewRequest()for all HTTP-level tests.