Documentation
¶
Overview ¶
Package jwt provides RS256 JWT signing, verification, and JWKS publishing with key rotation support.
RS256 only. The active key in the supplied KeyRing signs new tokens. Verification looks up the key by the mandatory "kid" header. The JWKS endpoint publishes all public keys so third-party services can verify tokens without sharing a secret.
Rotation procedure:
- Add new key with Active=false to the key set.
- Deploy — all instances now recognize the new key for verification.
- Flip the new key to Active=true (and old key to Active=false).
- Deploy — new tokens signed with new key, old tokens still verify.
- Wait access_token lifetime (15 min) for all old tokens to expire.
- Remove old key from set.
- Deploy.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Claims ¶
type Claims struct {
Sub string `json:"sub"`
Email string `json:"email"`
Name string `json:"name"`
Role string `json:"role"`
Tenant string `json:"tenant"`
AvatarURL string `json:"avatar_url"`
Audience []string `json:"aud,omitempty"`
IssuedAt int64 `json:"iat"`
ExpiresAt int64 `json:"exp"`
}
Claims holds the fields embedded in an access token.
func VerifyAccessToken ¶
func VerifyAccessToken(tokenStr string, kr *KeyRing, expectedTenant, expectedAudience string, requireAudience bool) (*Claims, error)
VerifyAccessToken verifies an RS256 access token and returns its claims. The token must carry a "kid" header that matches a key in the ring. Tokens without "kid" or with an unknown "kid" are rejected.
If expectedTenant is non-empty, the token's "tenant" claim must match it exactly; otherwise the token is rejected. Passing an empty expectedTenant disables the cross-tenant check.
Audience handling:
- If expectedAudience is empty, the "aud" claim is not inspected.
- If expectedAudience is non-empty and the token's "aud" claim is present, it must contain expectedAudience (a token MAY carry multiple audiences — the check passes when any of them matches).
- If expectedAudience is non-empty and the token has no "aud" claim, the token is rejected only when requireAudience is true. This gives callers a one-deploy migration window: ship the verifier with requireAudience=false, wait for all minted tokens to carry "aud", then flip requireAudience=true.
- A token whose "aud" claim is present but does not contain expectedAudience is ALWAYS rejected, regardless of requireAudience.
Tokens with a missing or zero "exp" claim are explicitly rejected: the underlying lestrrat-go jwt library treats an absent exp as "no expiration", which would otherwise produce unbounded-lifetime tokens.
type KeyRing ¶
type KeyRing struct {
// contains filtered or unexported fields
}
KeyRing manages multiple signing keys for rotation. Exactly one key is "active" and used to sign new tokens. All keys remain available for verification of previously-issued tokens.
func NewKeyRing ¶
func NewKeyRing(keys []SigningKey) (*KeyRing, error)
NewKeyRing creates a KeyRing from the given keys. Exactly one key must be marked Active. If no key is marked Active, the last key in the slice becomes active. If multiple keys are marked Active, an error is returned. An empty slice also returns an error.
func (*KeyRing) Active ¶
func (kr *KeyRing) Active() SigningKey
Active returns the key used to sign new tokens.
type SigningKey ¶
type SigningKey struct {
KID string
PrivateKey *rsa.PrivateKey // for signing
PublicKey *rsa.PublicKey // for verification + JWKS
Active bool
}
SigningKey holds an RSA key pair for JWT signing.
func GenerateKey ¶
func GenerateKey(kid string) (SigningKey, error)
GenerateKey creates a fresh RSA 2048-bit key pair and returns it as a SigningKey. The key is in-memory only.