JWT Security (security/jwt, security/claims, security/keys)
Packages
| Package | Import | Purpose |
|---|---|---|
security/jwt | .../security/jwt | JWT validator |
security/claims | .../security/claims | Claims model |
security/keys | .../security/keys | Key resolver contracts and constructors |
Claims Model (security/claims)
Claims exposes typed fields plus a private map for non-standard claims:
| Field | Type | Source JWT key |
|---|---|---|
Email | string | email |
Name | string | name |
Picture | string | picture |
Roles | []string | roles (bare key only) |
Private | map[string]any | All non-standard keys |
audaccepts string or array — normalized consistently.- Missing optional fields default to zero values without failing parsing.
- Namespaced keys (e.g.
https://example.com/roles) land inPrivate, not inRoles.
Validator (security/jwt)
validator, err := jwt.NewValidator(jwtConfig, resolver, jwt.WithTimeSource(fixedTime))
claims, err := validator.Validate(tokenString)
Policy checks
- Signature verification.
- Issuer (
iss) must match config. - Audience (
aud) must match config. - Algorithm (
alg) must be in allowlist. expandnbfevaluated using injected time source.
Error categories
| Category | Sentinel |
|---|---|
| Malformed token | jwt.ErrMalformed |
| Invalid signature | jwt.ErrInvalidSignature |
| Invalid issuer | jwt.ErrInvalidIssuer |
| Invalid audience | jwt.ErrInvalidAudience |
| Invalid method | jwt.ErrInvalidMethod |
| Expired token | jwt.ErrExpired |
| Not yet valid | jwt.ErrNotYetValid |
| Key resolution failure | jwt.ErrKeyResolution |
All errors are assertable via errors.Is/errors.As.
Key Resolvers (security/keys)
| Constructor | Usage |
|---|---|
keys.NewStaticResolver(secret []byte) | HS256 shared secret |
keys.NewRSAResolver(privateKey, keyID) | RS256 private key (signs and verifies) |
keys.NewRSAPublicKeyResolver(publicKey, keyID) | RS256 public key (verify only) |
Resolvers are deterministic and in-memory — no network I/O.
Key selection is by kid header field, falling back to default if not present.
HS256 Example
resolver := keys.NewStaticResolver([]byte("my-secret"))
validator, err := jwt.NewValidator(cfg.Security.Auth.JWT, resolver)
RS256 Example
// From config (recommended)
// UseServerSecurityFromConfig() handles this automatically
// Manual
resolver := keys.NewRSAPublicKeyResolver(pubKey, "my-key-id")
opts := jwt.ValidatorOptions{
Issuer: "my-service",
Methods: []string{"RS256"},
}
validator, err := jwt.NewValidatorWithOptions(opts, resolver)
Boundaries
- No Gin dependency.
- No app globals.
- No JWKS/OAuth provider adapters.