Table of contents
Part of nurago, a collection of independent Go packages for backend services.
import "github.com/tecnickcom/nurago/pkg/jwt"
Login, token issue, token verification and route authorisation, against net/http.
| Method | Purpose |
|---|---|
LoginHandler | validates credentials, issues a token |
RenewHandler | renews a valid token near expiry |
IsAuthorized | validates a bearer token for a protected handler |
Middleware | wraps a handler, injects verified claims into the context |
Authenticate | validates a bearer token, returns claims, for custom middleware |
IssueToken | mints a token outside the HTTP login flow |
VerifyToken | validates a raw token from any transport |
IssueToken and VerifyToken are the ones for non-HTTP paths: a WebSocket message, a queue payload, gRPC metadata.
HMAC only, and what that removes
Tokens are RFC 7515 compact JWS with RFC 7519 claims, signed with HMAC-SHA2 (RFC 7518 §3.2): HS256, HS384 or HS512. Nothing else. The implementation is self-contained on the Go standard library.
Narrowing the surface to one algorithm family removes the JWT vulnerabilities that come from algorithm agility rather than from cryptography.
alg=none. The specification permits an unsigned token type. Libraries that honour the header’s alg field have repeatedly accepted a token with alg set to none and no signature at all. There is no code path here that accepts an unsigned token.
RSA-to-HMAC confusion. The classic attack: a server configured for RS256 holds a public key; an attacker submits a token with alg changed to HS256, signed using that public key as the HMAC secret. A library that selects the algorithm from the header verifies it and accepts. Supporting only HMAC means there is no asymmetric key to substitute.
Unverified claim reading. Decoding the payload before checking the signature is trivially forgeable. The API does not expose claims except through a verifying call.
The cost of this narrowing is real: HMAC uses a shared secret, so every service that verifies a token can also mint one. For a fleet of mutually trusting services that is fine. For third-party verification, asymmetric signing is what you need, and this package does not do it.
Credential verification is yours
VerifyCredentialsFn is supplied by the caller, so the package has no opinion on where users live or how passwords are stored. Pair it with passwordhash for Argon2id storage.
Note the cost interaction: Argon2id verification is about 38 ms, so LoginHandler inherits that latency and the memory footprint that comes with it.
Renewal only near expiry
RenewHandler renews a token only when it is close to expiry.
A renew endpoint that refreshes any valid token turns a short-lived token into an unlimited one, since a stolen token can be renewed indefinitely and the expiry stops bounding the damage. Restricting renewal to a window near expiry keeps the original lifetime meaningful.
Short expiry is the primary control, because there is no revocation. A JWT is valid until it expires; nothing the server does invalidates an issued token short of rotating the signing key, which invalidates every token at once.
Costs
| Operation | ns/op | B/op | allocs/op |
|---|---|---|---|
IssueToken | 3,017 | 2,410 | 25 |
VerifyToken | 3,356 | 1,891 | 30 |
Measured with go test -bench on Go 1.27.1, linux/amd64, Intel Core i7-1260P. Run go test -bench=. ./pkg/jwt/ to reproduce them on your own hardware; the absolute values will differ, the ratios much less so.
About 3 microseconds either way, roughly 12,000 times cheaper than the Argon2id verification at login. Verifying a token per request is not a cost worth caching around.
Claims from the context
Middleware puts verified claims in the request context; ClaimsFromContext retrieves them. Handlers therefore see claims that have already been verified, with no way to reach an unverified token by accident.
Dependencies
This package reaches no external module: it uses only the Go standard library.
Links
- Reference documentation
- Source code
- In depth: The Classic JWT Attacks, Addressed by Construction in Go
- Password storage: passwordhash
- The security guide: /docs/security/
- All nurago packages