-
Release v1.0.2 Stable
released this
2026-05-18 14:16:42 -06:00 | 0 commits to main since this releasev1.0.2
code.nochebuena.dev/go/httpauthOverview
v1.0.2 closes three gaps discovered during integration testing of RBAC-guarded
endpoints: wildcard resource support inClaimsPermissionProvider, exact numeric
precision formath.MaxInt64permission masks, and JSON-formatted error responses
across the fullhttpauth-*middleware stack.No breaking changes. All v1.0.x consumers can upgrade with
go get.What Changed Since v1.0.1
Wildcard resource fallback in ClaimsPermissionProvider
ResolveMasknow checks the requested resource key first, then falls back to"*"if
the resource is absent from the JWT masks claim:// JWT masks claim: {"*": 9223372036854775807} provider := httpauth.NewClaimsPermissionProvider("masks") mask, _ := provider.ResolveMask(ctx, uid, "any_resource") // returns MaxInt64 — wildcard hitThis enables the
ADMINsystem role to carry a single wildcard entry and pass every
endpoint guard without enumerating resources. Per-resource entries take precedence over
"*"when both are present.json.Number precision for high-bit masks
ResolveMasknow handles three numeric types from JWT claims:Type Source math.MaxInt64safe?int64Go construction ✓ float64Standard JWT parsing (no option) ✗ (rounds to 2^63) json.Numberjwt.WithJSONNumber()✓ (decimal string parse) httpauth-jwtv1.0.2 usesjwt.WithJSONNumber()in allVerifyimplementations,
so thejson.Numberpath is now active for all tokens in the standard stack.Unified JSON error bodies —
WriteJSONErrorNew exported function:
func WriteJSONError(w http.ResponseWriter, status int, code, message string)Writes a structured JSON error body matching the
httputil.Errorformat:
Content-Type: application/jsonwith body{"code":"...","message":"..."}.All three auth middlewares in the stack now use this function:
Middleware Condition Status Code httpauth-jwtAuthMiddlewareMissing/invalid/expired token 401 UNAUTHENTICATEDEnrichmentMiddlewareNo uid in context 401 UNAUTHENTICATEDEnrichmentMiddlewareEnricher returns error 500 INTERNALAuthzMiddlewareNo identity in context 401 UNAUTHENTICATEDAuthzMiddlewarePermission denied or provider error 403 PERMISSION_DENIEDPreviously all three used
http.Errorwhich writestext/plain.AuthzMiddleware
also had the wrong code stringUNAUTHORIZEDinstead ofUNAUTHENTICATED.Dependency
Added
code.nochebuena.dev/go/xerrors v1.0.1for the code constants.Migration
No breaking changes.
go get code.nochebuena.dev/go/httpauth@v1.0.2Downloads
-
Release v1.0.0 Stable
released this
2026-05-07 23:09:23 -06:00 | 2 commits to main since this releasev1.0.0
code.nochebuena.dev/go/httpauthOverview
httpauthv1.0.0 commits the provider-agnostic middleware stack as stable and adds
ChainPermissionProvider— the last roadmap item. The module now ships three
rbac.PermissionProviderimplementations covering every common resolution pattern:
claims-embedded, TTL-cached, and chained (fast-path + fallback in the same request).Dependency bumped to
rbac v1.0.0.What Changed Since v0.1.0
New:
NewChainPermissionProviderfunc NewChainPermissionProvider(providers ...rbac.PermissionProvider) rbac.PermissionProviderTries each provider in order and returns the first non-zero mask. Errors propagate
immediately — subsequent providers are not consulted.Primary use case — JWT fast-path with DB fallback:
chain := httpauth.NewChainPermissionProvider( httpauth.NewClaimsPermissionProvider("permisos"), // JWT claims — no DB call httpauth.NewCachedPermissionProvider(dbProvider, valkeyCache, 5*time.Minute), // fallback ) r.With(httpauth.AuthzMiddleware(chain, "usuarios", rbac.Permission(1))).Get("/usuarios", h)When the JWT embeds permission masks (
customClaimsfromjwtauth.IssueTokenPair),
the chain returns immediately from the first provider. When the JWT has no embedded
masks (e.g. a token issued before permissions were cached in claims), the chain falls
through to the DB-backed provider transparently.Dependency bump
code.nochebuena.dev/go/rbac v0.9.0 → v1.0.0Full API (stable)
SetTokenData(ctx, uid, claims) context.Context— integration contract called
by provider-specific AuthMiddleware implementations.EnrichmentMiddleware(enricher, opts...) func(http.Handler) http.HandlerAuthzMiddleware(provider rbac.PermissionProvider, resource string, required rbac.Permission) func(http.Handler) http.HandlerNewClaimsPermissionProvider(claimsKey string) rbac.PermissionProvider— reads
bitmasks from JWT claims; handlesfloat64(JSON) andint64.NewCachedPermissionProvider(inner, cache, ttl) rbac.PermissionProvider— TTL
cache with silent fallthrough on cache error. Cache key:rbac:{uid}:{resource}.NewChainPermissionProvider(providers...) rbac.PermissionProvider— first
non-zero mask wins; errors propagate immediately. (New in v1.0.0)IdentityEnricherinterface —Enrich(ctx, uid, claims) (rbac.Identity, error)Cacheinterface —Get(ctx, key) (int64, bool, error)andSet(ctx, key, value, ttl) errorWithTenantHeader(header string) EnrichOptMigration from v0.1.0
No breaking changes. The only addition is
NewChainPermissionProvider.go get code.nochebuena.dev/go/httpauth@v1.0.0 go get code.nochebuena.dev/go/rbac@v1.0.0Downloads
-
Release v0.1.0 Stable
released this
2026-05-07 21:44:08 -06:00 | 3 commits to main since this releasev0.1.0
code.nochebuena.dev/go/httpauthOverview
httpauthprovides provider-agnostic HTTP middleware for identity enrichment and
RBAC authorization. It is the shared foundation for allhttpauth-*provider modules
(httpauth-firebase,httpauth-jwt, etc.) — they converge onrbac.Identityas
the output contract and callSetTokenDatato makeEnrichmentMiddlewareand
AuthzMiddlewarework without any further changes.The module ships two
rbac.PermissionProviderimplementations for the two common
architectures: JWT-embedded permissions (no runtime DB call) and TTL-cached runtime
resolution (for large or independently revocable permission sets).What's Included
SetTokenData(ctx context.Context, uid string, claims map[string]any) context.ContextInjects a verified uid and raw claims into the request context. Called by any
upstreamAuthMiddlewareafter token verification.EnrichmentMiddlewarereads
these values automatically. The context keys are unexported — callers interact
exclusively through this function and the downstream middleware.EnrichmentMiddleware(enricher IdentityEnricher, opts ...EnrichOpt) func(http.Handler) http.HandlerReads the uid and claims injected by any upstream
AuthMiddlewareviaSetTokenData,
calls the application-providedIdentityEnricher, and stores the resulting
rbac.Identityin context viarbac.SetInContext. Supports optional tenant header
extraction viaWithTenantHeader. Returns 401 if no uid is present; returns 500 if
the enricher fails.AuthzMiddleware(provider rbac.PermissionProvider, resource string, required rbac.Permission) func(http.Handler) http.HandlerReads
rbac.Identityfrom context (set byEnrichmentMiddleware), resolves the
permission mask via the providedrbac.PermissionProvider, and gates the request
against the required permission bit. Returns 401 if no identity is in context; returns
403 if the permission check fails or the provider errors. Usesrbac.PermissionProvider
directly — no local redefinition.NewClaimsPermissionProvider(claimsKey string) rbac.PermissionProviderReads pre-computed permission masks from JWT claims stored in context by
SetTokenData.
Expectsclaims[claimsKey]to be amap[string]anywhere each key is a resource name
and the value is the bitmask asint64orfloat64(JSON decodes numbers as float64).
Returns 0 without error if the claim is absent. No DB call, no network — suitable for
simple applications that embed permissions at token issuance time.Cacheinterface +NewCachedPermissionProvider(inner rbac.PermissionProvider, cache Cache, ttl time.Duration) rbac.PermissionProviderWraps any
rbac.PermissionProviderwith a TTL-based cache layer. Cache key format:
rbac:{uid}:{resource}. On cache miss, falls through toinnerand populates the
cache. On cache error, falls through silently — never fails due to cache unavailability.
TTL-based expiry only; for explicit invalidation, callers interact with theCache
directly using the known key format.Interfaces
IdentityEnricher— application-implemented; receives uid and raw claims, returnsrbac.IdentityCache— caching backend abstraction; implement with Valkey, Redis, or in-memory
Options
WithTenantHeader(header string) EnrichOpt— reads a tenant ID from a named request header and attaches it to the identity
Installation
go get code.nochebuena.dev/go/httpauth@v0.1.0Requires
code.nochebuena.dev/go/rbaconly. No external provider dependencies.Design Highlights
Provider-agnostic by design.
EnrichmentMiddlewareandAuthzMiddlewaredepend
only onSetTokenDatahaving been called upstream. AnyAuthMiddleware— Firebase,
self-issued JWT, API key, mTLS — is compatible without modifying the enrichment or
authorization layer. Provider-specific modules are thin adapters that callSetTokenData
and defer everything else to this module.rbac.PermissionProviderwithout redefinition.AuthzMiddlewareaccepts
rbac.PermissionProviderdirectly. Therbacpackage is the single source of truth
for this interface; local redefinition inhttpauth-firebasehas been removed.Two permission resolution strategies as first-class citizens.
ClaimsPermissionProvider
andCachedPermissionProvideraddress the two main architectures upfront. Applications
choose based on token size constraints and revocation requirements — not an afterthought.Cache falls through, never fails.
CachedPermissionProvidertreats cache errors as
misses. A Valkey outage degrades gracefully to the inner provider — the application
continues to serve requests with slightly higher latency. No circuit-breaker or fallback
logic required in application code.Minimal dependency surface. The only import is
code.nochebuena.dev/go/rbac. No
logger, no HTTP framework, no external service SDK. Errors surface as HTTP responses;
the enricher and provider implementations log at their own layer.Known Limitations & Edge Cases
ClaimsPermissionProvidercannot distinguish between "claim absent" and "mask is
genuinely 0". Both return(0, nil). Callers that need to distinguish should encode
a sentinel value or check for claim presence in the enricher.CachedPermissionProvideruses TTL-based expiry exclusively. After a role or
permission update, affected users retain cached masks until TTL expires. Set TTL
short enough to meet your consistency requirements (5 minutes is a typical default).
For immediate invalidation, deleterbac:{uid}:{resource}directly via yourCache.AuthzMiddlewaretreats provider errors and permission-check failures identically
(both return 403). The two cases are intentionally indistinguishable to callers.EnrichmentMiddlewarereturns a generic 500 if the enricher fails. Log the error
inside your enricher implementation; it is not surfaced to the HTTP client.
v0.1.0 → v1.0.0 Roadmap
- Evaluate adding
Delto theCacheinterface to support explicit invalidation viaCachedPermissionProvider - Consider an optional error observer hook for enricher and provider errors
- Add
ChainPermissionProviderfor OR-ing multiple providers (e.g. claims fast-path + DB fallback in the same request) - Production hardening across multiple deployed services
Downloads