ClaimsPermissionProvider.ResolveMask now falls back to the "*" resource key when the
exact resource is absent in the JWT masks claim. Specific resource takes precedence;
wildcard is the fallback. Enables ADMIN wildcard masks ({"*": MaxInt64}) to pass every
endpoint guard without per-resource entries.
Add json.Number handling alongside existing int64/float64 paths. json.Number is
produced by jwt.WithJSONNumber() (httpauth-jwt) and preserves math.MaxInt64 exactly —
float64 would round to 2^63 and overflow on cast back to int64.
Export WriteJSONError(w, status, code, message) — shared JSON error helper for all
httpauth-* provider packages. Ensures a consistent {"code":"...","message":"..."} body
and Content-Type: application/json across the full middleware stack.
AuthzMiddleware and EnrichmentMiddleware now use WriteJSONError with xerrors code
constants (UNAUTHENTICATED, PERMISSION_DENIED, INTERNAL) instead of http.Error which
writes text/plain. AuthzMiddleware also fixes a wrong code string: UNAUTHORIZED →
UNAUTHENTICATED (stable gRPC-aligned name, matching xerrors.ErrUnauthorized).
Add xerrors v1.0.1 as a direct dependency for the code constants.
Reviewed-on: #1
Co-authored-by: Rene Nochebuena Guerrero <rene@nochebuena.dev>
Co-committed-by: Rene Nochebuena Guerrero <rene@nochebuena.dev>
httpauth
Provider-agnostic HTTP middleware for identity enrichment and RBAC authorization.
Overview
Three composable func(http.Handler) http.Handler middleware functions and two rbac.PermissionProvider implementations:
| Component | Responsibility |
|---|---|
EnrichmentMiddleware |
Calls app-provided IdentityEnricher; stores rbac.Identity in context |
AuthzMiddleware |
Resolves permission mask via rbac.PermissionProvider; gates request |
ClaimsPermissionProvider |
Reads pre-computed masks from JWT claims — no DB call |
CachedPermissionProvider |
Wraps any provider with a TTL cache; falls through on miss or error |
SetTokenData |
Injects uid + claims from any verified token into the request context |
Any upstream AuthMiddleware that calls SetTokenData is compatible — Firebase, self-issued JWT, API key, etc.
Installation
require code.nochebuena.dev/go/httpauth v0.1.0
Usage
With JWT-embedded permissions (simple apps)
// Auth middleware (e.g. httpauth-jwt) calls httpauth.SetTokenData after verification.
// JWT claims include: { "permisos": { "usuarios": 515, "roles": 6 } }
r.Use(jwtauth.AuthMiddleware(signer, publicPaths, nil))
r.Use(httpauth.EnrichmentMiddleware(myEnricher))
claimsProvider := httpauth.NewClaimsPermissionProvider("permisos")
r.With(httpauth.AuthzMiddleware(claimsProvider, "usuarios", rbac.Permission(1))).
Get("/usuarios", handler)
With runtime resolution + cache (complex apps)
r.Use(jwtauth.AuthMiddleware(signer, publicPaths, nil))
r.Use(httpauth.EnrichmentMiddleware(myEnricher, httpauth.WithTenantHeader("X-Tenant-ID")))
dbProvider := myapp.NewDBPermissionProvider(db)
cachedProvider := httpauth.NewCachedPermissionProvider(dbProvider, valkeyCache, 5*time.Minute)
r.With(httpauth.AuthzMiddleware(cachedProvider, "usuarios", rbac.Permission(1))).
Get("/usuarios", handler)
Interfaces
IdentityEnricher
type IdentityEnricher interface {
Enrich(ctx context.Context, uid string, claims map[string]any) (rbac.Identity, error)
}
Implement in your application to load user data and return an rbac.Identity.
Cache
type Cache interface {
Get(ctx context.Context, key string) (int64, bool, error)
Set(ctx context.Context, key string, value int64, ttl time.Duration) error
}
Implement with Valkey, Redis, or any in-memory store. Cache keys follow the format rbac:{uid}:{resource}.
rbac.PermissionProvider (from the rbac package)
type PermissionProvider interface {
ResolveMask(ctx context.Context, uid, resource string) (rbac.PermissionMask, error)
}
AuthzMiddleware accepts any implementation — ClaimsPermissionProvider, CachedPermissionProvider, or your own.
SetTokenData
func SetTokenData(ctx context.Context, uid string, claims map[string]any) context.Context
Called by provider-specific AuthMiddleware implementations after token verification. EnrichmentMiddleware reads the injected values automatically.
Options
| Option | Description |
|---|---|
WithTenantHeader(header) |
Reads TenantID from the named request header. If absent, TenantID remains "". |
HTTP status codes
| Condition | Status |
|---|---|
| No uid in context (EnrichmentMiddleware) | 401 |
| Enricher error | 500 |
No rbac.Identity in context (AuthzMiddleware) |
401 |
| Permission denied or provider error | 403 |
Cache key format
CachedPermissionProvider uses rbac:{uid}:{resource} as the cache key. To invalidate manually, delete the key directly via your Cache implementation.