Why not a library
The case for stdlib over a JWT library:
- Audit surface. JWT libraries have been the source of classic security bugs — alg=none acceptance, RSA-key-confusion, signature-stripping. Every dependency is more code to read on every release.
- Surface area. The interface we actually need is small: HS256, exp, iat, aud. Pulling 5,000 lines of library to use 200 of them is the wrong trade.
- Reviewability. A 150-line stdlib implementation fits on three screens. The team can read it once and trust it. A library you didn’t write needs trust-by-proxy.
What the implementation looks like
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
)
type Issuer struct {
Secret []byte
Issuer string
Audience []string
TTL time.Duration
}
func (i *Issuer) Issue(userID, email string, roles []Role) (string, Claims, error) {
now := time.Now().UTC()
claims := Claims{
Subject: userID, Email: email, Roles: roles,
IssuedAt: now.Unix(), ExpiresAt: now.Add(i.TTL).Unix(),
Issuer: i.Issuer, Audience: i.Audience,
}
return encode(jwtHeader{Alg: "HS256", Typ: "JWT"}, claims, i.Secret), claims, nil
}
func (i *Issuer) Verify(token string) (Claims, error) {
parts := strings.Split(token, ".")
if len(parts) != 3 { return Claims{}, ErrInvalidToken }
// ... decode header, recompute signature with hmac.Equal,
// decode claims, check exp + iss + aud
}
hmac.Equal is the constant-time comparator — using bytes.Equal would be a timing side-channel.
Genie in production
Genie’s pkg/auth/jwt.go is ~170 lines including all the doc comments and the IssueWithActor variant for RFC 8693 token exchange. Three years in production. Zero CVEs. Zero “I wish we had used a library” moments. The audit footprint is exactly the code in front of you.
If you need RS256, the same story extends — about 50 more lines for crypto/rsa. If you need JWKS rotation, that’s a separate ~80 lines. Still well inside a single well-commented file.
When a library makes sense
- You need OIDC discovery + full id_token validation against a real IdP. The discovery machinery is enough code that a library pays back.
- You need many algorithms across many tenants.
- You don’t have anyone on the team who can read a 150-line crypto file with confidence.
For the common case in a Go service that issues its own tokens and verifies its own tokens: stdlib wins.