-
Release v1.0.2 Stable
released this
2026-05-18 14:39:23 -06:00 | 0 commits to main since this releasev1.0.2
code.nochebuena.dev/go/httpauth-jwtOverview
v1.0.2 adds
jwt.WithJSONNumber()to allVerifyimplementations, standardizes
AuthMiddlewareerror responses to JSON, and bumps thehttpauthdependency to
v1.0.2 to consume wildcard resource fallback, json.Number precision handling, and
the sharedWriteJSONErrorhelper.No breaking changes. All v1.0.x consumers can upgrade with
go get.What Changed Since v1.0.1
jwt.WithJSONNumber() in all Verify implementations
All three
Verifyimplementations now passjwt.WithJSONNumber()tojwt.Parse:hmacSigner.VerifyrsaSigner.VerifyrsaPublicVerifier.Verify
Without this option, the JWT library decodes all numbers as
float64.float64
cannot representmath.MaxInt64 = 9223372036854775807exactly — it rounds to2^63 = 9223372036854775808, which overflows back tomath.MinInt64when cast toint64.
This would corrupt theADMINwildcard permission mask stored in JWT claims.With
jwt.WithJSONNumber(), numbers are preserved asjson.Number(a decimal string
wrapper).json.Number.Int64()parses the exact value — no float64 round-trip, no
overflow.httpauth.ClaimsPermissionProvider(v1.0.2) handles all three types —
int64,float64,json.Number— so the chain is fully covered.JSON error body in AuthMiddleware
AuthMiddlewarenow returns a structured JSON body for all 401 responses by calling
httpauth.WriteJSONError. Previously it calledhttp.Errorwhich writestext/plain.The full middleware stack now produces consistent error responses:
Middleware Condition Status Code AuthMiddlewareMissing/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_DENIEDDependency bump
code.nochebuena.dev/go/httpauth v1.0.1 → v1.0.2Migration
No breaking changes. Callers that inspect decoded JWT claims should note that numeric
values now come back asjson.Numberinstead offloat64.go get code.nochebuena.dev/go/httpauth-jwt@v1.0.2 go get code.nochebuena.dev/go/httpauth@v1.0.2Downloads
-
Release v1.0.0 Stable
released this
2026-05-07 23:53:18 -06:00 | 2 commits to main since this releasev1.0.0
code.nochebuena.dev/go/httpauth-jwtOverview
httpauth-jwtprovides self-issued JWT authentication for Go HTTP services. It
covers the full token lifecycle: issuing access + refresh pairs, rotating refresh
tokens through aBlacklistinterface, and verifying Bearer tokens in an
AuthMiddlewarethat integrates directly with thehttpauthmiddleware stack.This is the JWT counterpart to
httpauth-firebase— both callhttpauth.SetTokenData
after token verification, making all downstream middleware (EnrichmentMiddleware,
AuthzMiddleware,ClaimsPermissionProvider) completely provider-agnostic.Released at v1.0.0 because it is designed for immediate production use across
multiple services.What's Included
Signing and verification
NewHMACSigner(secret []byte) Signer— HMAC-SHA256. For single-service
deployments.NewRSASigner(privateKey *rsa.PrivateKey) Signer— RSA-SHA256. For deployments
where the token issuer must be separated from verifiers.NewRSASignerFromPEM(pemKey []byte) (Signer, error)— loads PKCS#8 or PKCS#1
PEM private key from an environment variable or file.NewRSAPublicKeyVerifier(publicKey *rsa.PublicKey) Verifierand
NewRSAPublicKeyVerifierFromPEM(pemKey []byte) (Verifier, error)— for
microservices that verify tokens but never issue them.AuthMiddlewareaccepts the
Verifierinterface, so these services need no signing capability.Token issuance
IssueTokenPair(signer, uid, customClaims, cfg) (TokenPair, error)customClaimsare merged at the top level of the access token. This is the mechanism
for embedding per-resource permission masks:customClaims := map[string]any{ "permisos": map[string]any{"usuarios": int64(515)}, }The
httpauth.ClaimsPermissionProvider("permisos")reads these directly — no
database call on every request. On refresh, callers re-fetch fresh masks and embed
them in the new token.The refresh token carries only identity (
sub,iss,iat,exp,jti) and a
token family (fam). It never carries permission data.Refresh rotation
RefreshTokenPair(ctx, signer, refreshToken, blacklist, cfg, customClaims) (TokenPair, error)Full rotation protocol:
- Verify the refresh token signature and expiry.
- Check the JTI against the
Blacklist. - If revoked → return
ErrTokenRevoked(possible replay attack). - Revoke the old JTI with the token's remaining TTL.
- Issue a new pair with
customClaimsin the access token.
ErrTokenRevoked— sentinel error. Callers should respond with 401 and
prompt re-authentication when they see this.Blacklistinterface —IsRevokedandRevoke. Implementations backed by
Valkey or Redis are the expected use case. TTL onRevokekeeps the blacklist
bounded — entries naturally expire when the old token would have expired anyway.AuthMiddleware
AuthMiddleware(verifier Verifier, publicPaths []string) func(http.Handler) http.HandlerVerifies Bearer tokens and calls
httpauth.SetTokenData(ctx, uid, claims). Accepts
Verifier(notSigner) — the middleware only needs verification capability.Design Highlights
Verifier⊂Signer.AuthMiddlewareaccepts the narrowerVerifierinterface.
A service that receives tokens from a central issuer passes aNewRSAPublicKeyVerifier
— it never touches the private key. A service that also issues tokens passes its
Signerdirectly (which embedsVerifier).Claims at the top level. Access tokens use
jwt.MapClaimswith custom claims
merged at the top level, not nested under a wrapper key. This makes them directly
compatible withhttpauth.ClaimsPermissionProviderand any standard JWT introspection
tool.Refresh tokens carry no permission data. Permissions are re-fetched and
re-embedded on each rotation. This ensures role changes take effect on the next
refresh without requiring active token revocation for access tokens.Blacklist TTL = remaining token lifetime. Revoking a JTI with the token's
remaining lifetime means the blacklist entry expires at the exact moment the token
would have expired naturally. The blacklist stays small and requires no cleanup.ErrTokenRevokedis a sentinel. Callers useerrors.Is(err, httpauthjwt.ErrTokenRevoked)
to distinguish a replay attack from a general infrastructure error. Other errors
indicate connectivity or signing faults and may warrant different handling.Installation
go get code.nochebuena.dev/go/httpauth-jwt@v1.0.0 go get code.nochebuena.dev/go/httpauth@v1.0.0Downloads