Files
httpauth-firebase/CLAUDE.md
Rene Nochebuena 2c90fe22bf refactor(httpauth-firebase)!: delegate enrichment and authz to httpauth v0.1.0
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.
2026-05-07 21:57:01 -06:00

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-firebase because it imports Firebase directly. Other providers live in sibling modules (httpauth-jwt, etc.). All converge on rbac.Identity via the shared httpauth contract. The TokenVerifier interface is retained for unit-test mockability only.
  • Integration contract via httpauth.SetTokenData: AuthMiddleware stores uid and claims using httpauth.SetTokenData, which writes to context keys owned by the httpauth package. EnrichmentMiddleware (from httpauth) 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 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 in context by httpauth.SetTokenData under keys owned by the httpauth package. 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 (from httpauth) and passed to Enrich — the enricher is the correct place to resolve identity.
  • 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.
  • Do not redefine IdentityEnricher, Cache, or permission providers here. They live in code.nochebuena.dev/go/httpauth.

Testing Notes

  • compliance_test.go verifies at compile time that *mockVerifier satisfies TokenVerifier. Interface checks for IdentityEnricher, Cache, and permission providers live in code.nochebuena.dev/go/httpauth's own compliance test.
  • AuthMiddleware can be tested by injecting a mockVerifier that returns a controlled *auth.Token. No real Firebase project is needed.
  • EnrichmentMiddleware and AuthzMiddleware tests belong in the httpauth module, not here.
  • Use httptest.NewRecorder() and httptest.NewRequest() for all HTTP-level tests.