EnrichmentMiddleware, AuthzMiddleware, IdentityEnricher, PermissionProvider, and related types are removed from this module. They now live in code.nochebuena.dev/go/httpauth, the provider-agnostic middleware layer. AuthMiddleware is updated to call httpauth.SetTokenData, fulfilling the integration contract between provider-specific auth and generic middleware. This module now has a single responsibility: Firebase JWT verification. BREAKING CHANGE: IdentityEnricher, PermissionProvider, EnrichmentMiddleware, AuthzMiddleware, and WithTenantHeader are no longer exported from this package. Import code.nochebuena.dev/go/httpauth for those identifiers.
5.4 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 3 httpauth and external Firebase SDK)
Module: code.nochebuena.dev/go/httpauth-firebase
Direct imports: code.nochebuena.dev/go/httpauth, firebase.google.com/go/v4/auth
Transitive: code.nochebuena.dev/go/rbac (indirect, pulled in by httpauth)
httpauth-firebase does not import logz, httpmw, or httputil. It has no
logger parameter — errors are returned as HTTP responses, not logged here.
EnrichmentMiddleware, AuthzMiddleware, IdentityEnricher, WithTenantHeader,
Cache, and permission providers all live in code.nochebuena.dev/go/httpauth.
This module only provides AuthMiddleware and the TokenVerifier interface.
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-jwt, etc.). All converge onrbac.Identityvia the sharedhttpauthcontract. TheTokenVerifierinterface is retained for unit-test mockability only. - Integration contract via
httpauth.SetTokenData:AuthMiddlewarestoresuidandclaimsusinghttpauth.SetTokenData, which writes to context keys owned by thehttpauthpackage.EnrichmentMiddleware(fromhttpauth) reads them. This is the explicit integration point between provider-specific and provider-agnostic middleware. - Single responsibility: This module only verifies Firebase tokens. Identity
enrichment, RBAC, and caching all live in
code.nochebuena.dev/go/httpauth.
Patterns
Full stack (most routes):
import (
httpauth "code.nochebuena.dev/go/httpauth-firebase"
httpauthmw "code.nochebuena.dev/go/httpauth"
)
r.Use(httpauth.AuthMiddleware(firebaseAuthClient, []string{"/health", "/metrics/*"}))
r.Use(httpauthmw.EnrichmentMiddleware(userEnricher, httpauthmw.WithTenantHeader("X-Tenant-ID")))
// Per-route RBAC:
r.With(httpauthmw.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.DisplayName, 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 (defined in httpauth, not here):
// httpauthmw.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), 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 in context byhttpauth.SetTokenDataunder keys owned by thehttpauthpackage. Userbac.FromContextto read the enriched identity. - Do not store application-specific data in Firebase custom claims and bypass the
IdentityEnricher. Claims are read byEnrichmentMiddleware(fromhttpauth) and passed toEnrich— the enricher is the correct place to resolve identity. - Do not import
httpauth-firebasefrom service or domain layers. It is a transport package. Dependency should flow inward: handler → service, never service → handler/middleware. - Do not redefine
IdentityEnricher,Cache, or permission providers here. They live incode.nochebuena.dev/go/httpauth.
Testing Notes
compliance_test.goverifies at compile time that*mockVerifiersatisfiesTokenVerifier. Interface checks forIdentityEnricher,Cache, and permission providers live incode.nochebuena.dev/go/httpauth's own compliance test.AuthMiddlewarecan be tested by injecting amockVerifierthat returns a controlled*auth.Token. No real Firebase project is needed.EnrichmentMiddlewareandAuthzMiddlewaretests belong in thehttpauthmodule, not here.- Use
httptest.NewRecorder()andhttptest.NewRequest()for all HTTP-level tests.