• v0.1.0 18e5a16f7e

    Rene Nochebuena released this 2026-05-07 21:44:08 -06:00 | 2 commits to main since this release

    v0.1.0

    code.nochebuena.dev/go/httpauth

    Overview

    httpauth provides provider-agnostic HTTP middleware for identity enrichment and
    RBAC authorization. It is the shared foundation for all httpauth-* provider modules
    (httpauth-firebase, httpauth-jwt, etc.) — they converge on rbac.Identity as
    the output contract and call SetTokenData to make EnrichmentMiddleware and
    AuthzMiddleware work without any further changes.

    The module ships two rbac.PermissionProvider implementations 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.Context

    Injects a verified uid and raw claims into the request context. Called by any
    upstream AuthMiddleware after token verification. EnrichmentMiddleware reads
    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.Handler

    Reads the uid and claims injected by any upstream AuthMiddleware via SetTokenData,
    calls the application-provided IdentityEnricher, and stores the resulting
    rbac.Identity in context via rbac.SetInContext. Supports optional tenant header
    extraction via WithTenantHeader. 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.Handler

    Reads rbac.Identity from context (set by EnrichmentMiddleware), resolves the
    permission mask via the provided rbac.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. Uses rbac.PermissionProvider
    directly — no local redefinition.

    NewClaimsPermissionProvider(claimsKey string) rbac.PermissionProvider

    Reads pre-computed permission masks from JWT claims stored in context by SetTokenData.
    Expects claims[claimsKey] to be a map[string]any where each key is a resource name
    and the value is the bitmask as int64 or float64 (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.

    Cache interface + NewCachedPermissionProvider(inner rbac.PermissionProvider, cache Cache, ttl time.Duration) rbac.PermissionProvider

    Wraps any rbac.PermissionProvider with a TTL-based cache layer. Cache key format:
    rbac:{uid}:{resource}. On cache miss, falls through to inner and 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 the Cache
    directly using the known key format.

    Interfaces

    • IdentityEnricher — application-implemented; receives uid and raw claims, returns rbac.Identity
    • Cache — 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.0
    

    Requires code.nochebuena.dev/go/rbac only. No external provider dependencies.

    Design Highlights

    Provider-agnostic by design. EnrichmentMiddleware and AuthzMiddleware depend
    only on SetTokenData having been called upstream. Any AuthMiddleware — Firebase,
    self-issued JWT, API key, mTLS — is compatible without modifying the enrichment or
    authorization layer. Provider-specific modules are thin adapters that call SetTokenData
    and defer everything else to this module.

    rbac.PermissionProvider without redefinition. AuthzMiddleware accepts
    rbac.PermissionProvider directly. The rbac package is the single source of truth
    for this interface; local redefinition in httpauth-firebase has been removed.

    Two permission resolution strategies as first-class citizens. ClaimsPermissionProvider
    and CachedPermissionProvider address the two main architectures upfront. Applications
    choose based on token size constraints and revocation requirements — not an afterthought.

    Cache falls through, never fails. CachedPermissionProvider treats 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

    • ClaimsPermissionProvider cannot 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.
    • CachedPermissionProvider uses 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, delete rbac:{uid}:{resource} directly via your Cache.
    • AuthzMiddleware treats provider errors and permission-check failures identically
      (both return 403). The two cases are intentionally indistinguishable to callers.
    • EnrichmentMiddleware returns 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 Del to the Cache interface to support explicit invalidation via CachedPermissionProvider
    • Consider an optional error observer hook for enricher and provider errors
    • Add ChainPermissionProvider for OR-ing multiple providers (e.g. claims fast-path + DB fallback in the same request)
    • Production hardening across multiple deployed services
    Downloads