Files
httpauth/CLAUDE.md
Rene Nochebuena 18e5a16f7e feat(httpauth): initial release — provider-agnostic HTTP auth middleware
Provides SetTokenData for upstream AuthMiddleware implementations,
EnrichmentMiddleware and AuthzMiddleware compatible with any provider that
calls SetTokenData, ClaimsPermissionProvider for JWT-embedded permissions,
and CachedPermissionProvider for TTL-backed runtime resolution via any
Cache implementation.
2026-05-07 21:37:25 -06:00

4.8 KiB

httpauth

Provider-agnostic HTTP middleware for identity enrichment and RBAC authorization.

Purpose

httpauth is the shared foundation for all httpauth-* provider modules. It provides EnrichmentMiddleware, AuthzMiddleware, two rbac.PermissionProvider implementations (ClaimsPermissionProvider and CachedPermissionProvider), and SetTokenData — the bridge between a provider-specific AuthMiddleware and the rest of the auth stack.

Any AuthMiddleware that calls SetTokenData after token verification is compatible. Downstream code reads identity exclusively via rbac.FromContext — no provider type leaks past the middleware boundary.

Tier & Dependencies

Tier: 3 (transport auth layer; depends only on Tier 0 rbac; no external SDK) Module: code.nochebuena.dev/go/httpauth Direct imports: code.nochebuena.dev/go/rbac only

httpauth does not import logz, httpmw, httputil, Firebase, or any JWT library. It has no logger parameter — errors are returned as HTTP responses.

Key Design Decisions

  • rbac.PermissionProvider without redefinition: AuthzMiddleware accepts rbac.PermissionProvider directly. rbac is the single source of truth for this interface. Provider-specific modules (e.g. httpauth-firebase) previously defined their own PermissionProvider locally — that duplication is removed.
  • SetTokenData as the integration contract: Provider-specific AuthMiddleware implementations call SetTokenData(ctx, uid, claims) after verifying the token. The context keys are unexported typed structs. EnrichmentMiddleware reads them via the unexported helpers getUID and getClaims in the same package.
  • Two permission strategies: ClaimsPermissionProvider (JWT-embedded, no DB call) and CachedPermissionProvider (TTL-backed runtime resolution) are first-class implementations. Choose based on token size and revocation requirements.
  • Cache falls through on error: CachedPermissionProvider treats cache errors as misses — a cache outage degrades gracefully to the inner provider.

Patterns

Full stack with self-issued JWT:

r.Use(jwtauth.AuthMiddleware(signer, publicPaths, nil))
r.Use(httpauth.EnrichmentMiddleware(myEnricher, httpauth.WithTenantHeader("X-Tenant-ID")))

// Simple app — permissions embedded in JWT:
claimsProvider := httpauth.NewClaimsPermissionProvider("permisos")
r.With(httpauth.AuthzMiddleware(claimsProvider, "usuarios", rbac.Permission(1))).
    Get("/usuarios", handler)

// Complex app — runtime resolution with cache:
cachedProvider := httpauth.NewCachedPermissionProvider(dbProvider, valkeyCache, 5*time.Minute)
r.With(httpauth.AuthzMiddleware(cachedProvider, "usuarios", rbac.Permission(1))).
    Get("/usuarios", handler)

Provider-specific AuthMiddleware calling SetTokenData:

// Inside httpauth-jwt or httpauth-firebase AuthMiddleware, after token verification:
ctx := httpauth.SetTokenData(r.Context(), verified.UID, verified.Claims)
next.ServeHTTP(w, r.WithContext(ctx))

Reading identity in a handler:

identity, ok := rbac.FromContext(r.Context())
if !ok {
    // should not happen if EnrichmentMiddleware is in the chain
}

Implementing Cache (e.g. with Valkey):

type valkeyCache struct{ client valkey.Client }

func (c *valkeyCache) Get(ctx context.Context, key string) (int64, bool, error) { ... }
func (c *valkeyCache) Set(ctx context.Context, key string, val int64, ttl time.Duration) error { ... }

Cache key for manual invalidation: rbac:{uid}:{resource}

What to Avoid

  • Do not call SetTokenData from application or domain layer code. It is the exclusive responsibility of provider-specific AuthMiddleware implementations.
  • 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 import httpauth from service or domain layers. It is a transport package.
  • Do not define a local PermissionProvider interface in provider modules that import this package — use rbac.PermissionProvider directly.

Testing Notes

  • compliance_test.go verifies at compile time that mock types satisfy IdentityEnricher and Cache, and that rbac.PermissionProvider is satisfied by the two built-in provider implementations.
  • EnrichmentMiddleware tests use injectTokenData(uid, claims, next) — a helper that calls SetTokenData to bypass a real upstream AuthMiddleware.
  • AuthzMiddleware tests pre-populate context with rbac.SetInContext — no enrichment middleware needed.
  • ClaimsPermissionProvider tests exercise both float64 (JSON decode) and int64 paths for the mask value.
  • CachedPermissionProvider tests exercise cache hit, miss, cache error fallthrough, and inner provider error propagation.