jwt

package
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 6, 2026 License: MIT Imports: 25 Imported by: 0

Documentation

Overview

Package jwt implements JSON Web Tokens (RFC 7519) and the JSON Web Signature (RFC 7515) compact serialization from the Go standard library only — no third-party JOSE dependency. It provides signing and verification for HMAC (HS256/384/512), RSA-PKCS1v15 (RS256/384/512), ECDSA (ES256/384/512, fixed- width R||S per RFC 7518) and EdDSA (Ed25519), with an explicit algorithm allowlist on the verify path to make algorithm-confusion attacks impossible.

Security posture (see plans/phase-4-jwt.md):

  • A Verifier is bound to a fixed (algorithm allowlist, key material) pair. A token whose header "alg" is not in the allowlist is rejected before any cryptographic operation runs, so algorithm-confusion (e.g. presenting an HS256 token to an RSA verifier and using the public key bytes as the MAC secret) is impossible by construction.
  • The string "none" is never a valid Algorithm and is rejected both at verifier construction and on decode.
  • ECDSA signatures are JOSE-native fixed-width R||S; DER-encoded signatures are rejected.
  • The raw token bytes are never placed in an error string, log line, or Authentication.Details(); errors describe only the failure class.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrMalformedToken indicates the compact serialization is structurally
	// invalid (e.g. an empty segment).
	ErrMalformedToken = errors.New("jwt: malformed compact serialization")
	// ErrNotJWS indicates the serialization does not have exactly three
	// dot-separated segments (a JWE 5-segment token is out of scope).
	ErrNotJWS = errors.New("jwt: not a JWS compact serialization (expected 3 segments)")
	// ErrUnsupportedAlg indicates the algorithm is "none", empty, or otherwise
	// not supported.
	ErrUnsupportedAlg = errors.New("jwt: unsupported or disallowed algorithm")
	// ErrUnsupportedCrit indicates an unrecognized critical header parameter.
	ErrUnsupportedCrit = errors.New("jwt: unrecognized critical header parameter")
	// ErrAlgMismatch indicates the token's alg is not in the verifier allowlist.
	ErrAlgMismatch = errors.New("jwt: token alg not in verifier allowlist")
	// ErrKeyTypeMismatch indicates the supplied key type does not match the
	// algorithm (or, for ECDSA, the curve does not match).
	ErrKeyTypeMismatch = errors.New("jwt: key type does not match algorithm")
	// ErrSignatureInvalid indicates the signature did not verify.
	ErrSignatureInvalid = errors.New("jwt: signature invalid")
	// ErrTokenExpired indicates the token is past its exp (accounting for skew).
	ErrTokenExpired = errors.New("jwt: token expired")
	// ErrTokenNotYetValid indicates the token's nbf is in the future.
	ErrTokenNotYetValid = errors.New("jwt: token not yet valid (nbf)")
	// ErrIssuedInFuture indicates the token's iat is in the future beyond skew.
	ErrIssuedInFuture = errors.New("jwt: token issued in the future (iat)")
	// ErrClaimMissing indicates a required claim is absent.
	ErrClaimMissing = errors.New("jwt: required claim missing")
	// ErrIssuerMismatch indicates the iss claim does not match the required value.
	ErrIssuerMismatch = errors.New("jwt: issuer mismatch")
	// ErrAudienceMismatch indicates the aud claim does not contain the required value.
	ErrAudienceMismatch = errors.New("jwt: audience mismatch")
	// ErrKeyNotFound indicates no configured key matches the token's kid.
	ErrKeyNotFound = errors.New("jwt: no key matches kid")
)

Sentinel errors. They are wrap-friendly (callers match with errors.Is) and never embed token contents.

View Source
var ErrJWKSFetch = errors.New("jwt: jwks fetch failed")

ErrJWKSFetch indicates the JWKS endpoint returned a non-2xx status or could not be reached.

View Source
var ErrJWKSTooLarge = errors.New("jwt: jwks response too large")

ErrJWKSTooLarge indicates the JWKS response exceeded the configured size cap.

Functions

func Decode

func Decode(raw string, v Verifier, validator *Validator) (MapClaims, RegisteredClaims, error)

Decode parses and verifies the signature of raw against v, then (if validator is non-nil) validates the registered claims. It returns the claims only on full success; it NEVER returns claims from an unverified or invalid token.

func ES256Verify

func ES256Verify(pub *ecdsa.PublicKey) *ecdsaVerifyingKey

ES256Verify builds a VerifyingKey for ES256 from a P-256 public key.

func ES384Verify

func ES384Verify(pub *ecdsa.PublicKey) *ecdsaVerifyingKey

ES384Verify builds a VerifyingKey for ES384 (P-384).

func ES512Verify

func ES512Verify(pub *ecdsa.PublicKey) *ecdsaVerifyingKey

ES512Verify builds a VerifyingKey for ES512 (P-521).

func EdDSASign

func EdDSASign(priv ed25519.PrivateKey) *ed25519SigningKey

EdDSASign builds an Ed25519 SigningKey. The key is copied defensively.

func EdDSAVerify

func EdDSAVerify(pub ed25519.PublicKey) *ed25519VerifyingKey

EdDSAVerify builds an Ed25519 VerifyingKey. The key is copied defensively and must be ed25519.PublicKeySize bytes.

func Encode

func Encode(s Signer, claims MapClaims) (string, error)

Encode marshals the header (with the signer's algorithm and optional kid) and the claims, signs the signing-input, and returns the compact JWS.

func EncodeRegistered

func EncodeRegistered(s Signer, rc RegisteredClaims, extra MapClaims) (string, error)

EncodeRegistered encodes the registered claims merged with any extra custom claims (extra wins on key collision is avoided: registered claims take precedence for the standard names).

func NewES256

func NewES256(priv *ecdsa.PrivateKey) *ecdsaSigningKey

NewES256 builds an ECDSA-P-256-SHA-256 SigningKey. The private key MUST be on P-256; a mismatch yields ErrKeyTypeMismatch at sign time.

func NewES384

func NewES384(priv *ecdsa.PrivateKey) *ecdsaSigningKey

NewES384 builds an ECDSA-P-384-SHA-384 SigningKey (requires P-384).

func NewES512

func NewES512(priv *ecdsa.PrivateKey) *ecdsaSigningKey

NewES512 builds an ECDSA-P-521-SHA-512 SigningKey (requires P-521; note P-521, not P-512).

func NewHS256

func NewHS256(secret []byte) *hmacKey

NewHS256 builds an HMAC-SHA-256 SigningKey/VerifyingKey from a secret. The secret should be at least 32 bytes for HS256 (RFC 7518 §3.2 recommends a key at least as long as the hash output); a shorter key is accepted but weak.

func NewHS384

func NewHS384(secret []byte) *hmacKey

NewHS384 builds an HMAC-SHA-384 carrier (recommended secret >= 48 bytes).

func NewHS512

func NewHS512(secret []byte) *hmacKey

NewHS512 builds an HMAC-SHA-512 carrier (recommended secret >= 64 bytes).

func NewRS256

func NewRS256(priv *rsa.PrivateKey) *rsaSigningKey

NewRS256 builds an RSASSA-PKCS1-v1_5-SHA-256 SigningKey from an RSA private key.

func NewRS384

func NewRS384(priv *rsa.PrivateKey) *rsaSigningKey

NewRS384 builds an RSASSA-PKCS1-v1_5-SHA-384 SigningKey.

func NewRS512

func NewRS512(priv *rsa.PrivateKey) *rsaSigningKey

NewRS512 builds an RSASSA-PKCS1-v1_5-SHA-512 SigningKey.

func RS256Verify

func RS256Verify(pub *rsa.PublicKey) *rsaVerifyingKey

RS256Verify builds a VerifyingKey for RS256 from an RSA public key.

func RS384Verify

func RS384Verify(pub *rsa.PublicKey) *rsaVerifyingKey

RS384Verify builds a VerifyingKey for RS384.

func RS512Verify

func RS512Verify(pub *rsa.PublicKey) *rsaVerifyingKey

RS512Verify builds a VerifyingKey for RS512.

Types

type Algorithm

type Algorithm string

Algorithm is the JOSE "alg" header value. "none" is intentionally absent from the constant set and is never a valid Algorithm.

const (
	HS256 Algorithm = "HS256"
	HS384 Algorithm = "HS384"
	HS512 Algorithm = "HS512"
	RS256 Algorithm = "RS256"
	RS384 Algorithm = "RS384"
	RS512 Algorithm = "RS512"
	ES256 Algorithm = "ES256"
	ES384 Algorithm = "ES384"
	ES512 Algorithm = "ES512"
	EdDSA Algorithm = "EdDSA"
)

Supported JWS algorithms.

type Audience

type Audience []string

Audience is the RFC 7519 §4.1.3 "aud" claim. It marshals to a bare string when it holds a single value and to a JSON array otherwise, and unmarshals from either form.

func (Audience) Contains

func (a Audience) Contains(v string) bool

Contains reports whether v is one of the audience values.

func (Audience) MarshalJSON

func (a Audience) MarshalJSON() ([]byte, error)

MarshalJSON emits a bare string for a single-element audience, a JSON array otherwise. An empty audience marshals to null (and is omitted when the field carries omitempty).

func (*Audience) UnmarshalJSON

func (a *Audience) UnmarshalJSON(b []byte) error

UnmarshalJSON accepts either a JSON string or a JSON array of strings.

type Clock

type Clock interface {
	Now() time.Time
}

Clock is the injectable time source. The default systemClock uses time.Now.

type Header struct {
	Alg  string   `json:"alg"`
	Typ  string   `json:"typ,omitempty"`
	Kid  string   `json:"kid,omitempty"`
	Cty  string   `json:"cty,omitempty"`
	Crit []string `json:"crit,omitempty"`
}

Header is the JOSE protected header (RFC 7515 §4). The jku/x5u/jwk/x5c parameters are intentionally NOT fields: they are ignored and never trusted, which closes the classic JWKS-via-token SSRF and embedded-key trust holes.

type JWK

type JWK struct {
	Kty string `json:"kty"`
	Kid string `json:"kid,omitempty"`
	Alg string `json:"alg,omitempty"`
	Use string `json:"use,omitempty"`

	// RSA public params.
	N string `json:"n,omitempty"`
	E string `json:"e,omitempty"`

	// EC public params.
	Crv string `json:"crv,omitempty"`
	X   string `json:"x,omitempty"`
	Y   string `json:"y,omitempty"`

	// oct (symmetric) secret.
	K string `json:"k,omitempty"`
}

JWK is a single JSON Web Key (RFC 7517). Only the fields required to reconstruct a public verifying key (or an oct secret) are parsed.

func ParseJWK

func ParseJWK(b []byte) (JWK, error)

ParseJWK parses a single JWK from its JSON encoding.

func (JWK) VerifyingKey

func (k JWK) VerifyingKey() (VerifyingKey, error)

VerifyingKey reconstructs the Go verifying key (or oct secret carrier) from a JWK, defaulting the algorithm from the key type and curve. The returned VerifyingKey's Algorithm() reports the alg the key can verify, which the JWKSClient cross-checks against the verifier allowlist.

For an EC key the public point is validated to be on the named curve (rejecting invalid-curve attack inputs). An unknown kty, a malformed base64url parameter, or an out-of-range RSA exponent is rejected.

type JWKSClient

type JWKSClient struct {
	// contains filtered or unexported fields
}

JWKSClient fetches a JWK Set from an operator-configured URL and caches it, refreshing on TTL expiry and on a kid miss (throttled by a minimum refresh interval). It implements KeySelector so it plugs directly into NewVerifier.

The fetch URL is operator-configured and is NEVER derived from a token claim or header (no jku/x5u following), which closes the classic JWKS SSRF. The response body is read through an io.LimitReader with a hard cap. The cache is per-instance and guarded by a sync.RWMutex.

func NewJWKSClient

func NewJWKSClient(jwksURL string, opts ...JWKSOption) (*JWKSClient, error)

NewJWKSClient builds a JWKSClient for the given JWKS URL. It validates the URL scheme (https unless AllowInsecureURL(true) is set).

func (*JWKSClient) Refresh

func (c *JWKSClient) Refresh(ctx context.Context) error

Refresh forces a fetch, bypassing the min-refresh throttle.

func (*JWKSClient) Select

func (c *JWKSClient) Select(kid string, alg Algorithm) (VerifyingKey, error)

Select implements KeySelector. It returns the cached key for kid (an empty kid selects the sole key when exactly one is cached). On a stale cache or a kid miss it triggers at most one throttled refresh, then re-checks.

type JWKSOption

type JWKSOption func(*jwksConfig)

JWKSOption configures a JWKSClient.

func AllowInsecureURL

func AllowInsecureURL(allow bool) JWKSOption

AllowInsecureURL permits an http:// JWKS URL (default false: https only). It exists for localhost test fixtures and should not be used in production.

func WithCacheTTL

func WithCacheTTL(d time.Duration) JWKSOption

WithCacheTTL sets how long a fetched key set is served from cache before a lazy refresh (default 10m).

func WithHTTPClient

func WithHTTPClient(c *http.Client) JWKSOption

WithHTTPClient injects the HTTP client used for fetches. The default is a bounded client with a 5s timeout (never http.DefaultClient).

func WithMaxResponseBytes

func WithMaxResponseBytes(n int64) JWKSOption

WithMaxResponseBytes caps the JWKS response body size (default 256 KiB).

func WithMinRefreshInterval

func WithMinRefreshInterval(d time.Duration) JWKSOption

WithMinRefreshInterval sets the minimum interval between on-miss refreshes, throttling refreshes triggered by unknown kids (default 1m).

type JWKSet

type JWKSet struct {
	Keys []JWK `json:"keys"`
}

JWKSet is a JWK Set (RFC 7517 §5).

func ParseJWKSet

func ParseJWKSet(b []byte) (JWKSet, error)

ParseJWKSet parses a JWK Set from its JSON encoding.

type KeySelector

type KeySelector interface {
	Select(kid string, alg Algorithm) (VerifyingKey, error)
}

KeySelector resolves a VerifyingKey by kid (and alg). An empty kid selects the sole configured key when exactly one matches the algorithm.

type MapClaims

type MapClaims map[string]any

MapClaims is the full claim set: registered claims plus arbitrary custom claims, preserving unknown fields. Typed accessors return a zero value and false on absence or type mismatch.

func (MapClaims) Audience

func (m MapClaims) Audience() Audience

Audience returns the aud claim parsed from either a string or an array.

func (MapClaims) GetString

func (m MapClaims) GetString(name string) (string, bool)

GetString returns the named claim as a string, or ("", false) when absent or not a string.

func (MapClaims) GetStringSlice

func (m MapClaims) GetStringSlice(name string) ([]string, bool)

GetStringSlice returns the named claim as a string slice. A single string is tolerated and returned as a one-element slice (it is NOT space-split here; space-splitting of the scope claim is a mapper concern). A JSON array of strings is returned element-wise. Returns (nil, false) on absence or a non-string/array shape.

func (MapClaims) Registered

func (m MapClaims) Registered() (RegisteredClaims, error)

Registered extracts the RFC 7519 registered claims from the map. Numeric dates tolerate integer or float JSON numbers (json decodes them as float64).

type NumericDate

type NumericDate struct {
	time.Time
}

NumericDate wraps time.Time with the RFC 7519 §2 "seconds since epoch" JSON representation. It tolerates a fractional value on decode and emits an integer on encode.

func NewNumericDate

func NewNumericDate(t time.Time) *NumericDate

NewNumericDate builds a *NumericDate from a time.Time (truncated to seconds on the wire).

func (NumericDate) MarshalJSON

func (n NumericDate) MarshalJSON() ([]byte, error)

MarshalJSON emits the integer seconds since the Unix epoch.

func (*NumericDate) UnmarshalJSON

func (n *NumericDate) UnmarshalJSON(b []byte) error

UnmarshalJSON parses a numeric (integer or float) seconds-since-epoch value.

type RegisteredClaims

type RegisteredClaims struct {
	Issuer    string       `json:"iss,omitempty"`
	Subject   string       `json:"sub,omitempty"`
	Audience  Audience     `json:"aud,omitempty"`
	ExpiresAt *NumericDate `json:"exp,omitempty"`
	NotBefore *NumericDate `json:"nbf,omitempty"`
	IssuedAt  *NumericDate `json:"iat,omitempty"`
	ID        string       `json:"jti,omitempty"`
}

RegisteredClaims are the RFC 7519 §4.1 registered claims.

type Signer

type Signer interface {
	Algorithm() Algorithm
	// Sign returns the raw signature bytes for the signing-input.
	Sign(signingInput []byte) ([]byte, error)
	// contains filtered or unexported methods
}

Signer signs a signing-input and reports its algorithm.

func NewSigner

func NewSigner(key SigningKey, kid string) (Signer, error)

NewSigner builds a Signer from a per-algorithm SigningKey and an optional kid (written into the header on Encode).

type SigningKey

type SigningKey interface {
	Algorithm() Algorithm
	// contains filtered or unexported methods
}

SigningKey is the per-algorithm signing carrier produced by HS256/RS256/…/ EdDSASign. It reports its algorithm and signs a signing-input. It is the internal contract consumed by NewSigner/Encode.

type ValidationOption

type ValidationOption func(*Validator)

ValidationOption configures a Validator.

func WithClock

func WithClock(c Clock) ValidationOption

WithClock injects the time source used for exp/nbf/iat checks.

func WithLeeway

func WithLeeway(skew time.Duration) ValidationOption

WithLeeway sets the permitted clock skew. It must be in [0, 5m]; a value outside that range panics as a programmer error (a huge skew silently defeats expiry validation, and a negative skew is meaningless).

func WithRequireExpiry

func WithRequireExpiry(required bool) ValidationOption

WithRequireExpiry sets whether a token must carry an exp claim. The default is true (reject a token with no exp).

SECURITY: Setting this false accepts never-expiring tokens — a stolen token is then valid forever and cannot be aged out. Only do so for an atypical issuer that genuinely omits exp, and compensate with short-lived issuance or revocation elsewhere. Never set it false to "fix" a clock-skew or expired-token error.

func WithRequiredAudience

func WithRequiredAudience(aud string) ValidationOption

WithRequiredAudience requires the aud claim to contain aud. An empty string clears the requirement.

func WithRequiredIssuer

func WithRequiredIssuer(iss string) ValidationOption

WithRequiredIssuer requires the iss claim to equal iss exactly (no normalization, no prefix match). An empty string clears the requirement.

func WithValidateIAT

func WithValidateIAT(required bool) ValidationOption

WithValidateIAT sets whether the iat claim, if present, is rejected when it is in the future beyond the skew. Presence remains optional. Default false.

type Validator

type Validator struct {
	// contains filtered or unexported fields
}

Validator checks the registered claims of a verified token with a configurable clock skew (leeway) and required-claim policy. It is read-only after construction and safe for concurrent use.

func NewValidator

func NewValidator(opts ...ValidationOption) *Validator

NewValidator builds a Validator. Defaults: system clock, 60s leeway, expiry required, issuer/audience unchecked, iat not validated.

func (*Validator) Validate

func (v *Validator) Validate(c RegisteredClaims) error

Validate checks exp/nbf/iat (with skew), the required issuer/audience, and the require-expiry policy. It returns a wrapped sentinel error on failure.

type Verifier

type Verifier interface {
	// Verify checks that h.Alg is in the allowlist and the signature is valid
	// for the key selected by h.Kid. It returns ErrAlgMismatch /
	// ErrUnsupportedAlg / ErrSignatureInvalid / ErrKeyNotFound.
	Verify(h Header, signingInput, sig []byte) error
	// Allowed reports the immutable algorithm allowlist.
	Allowed() []Algorithm
}

Verifier verifies a signature for a fixed set of allowed algorithms and keys. Algorithm-confusion is impossible: an alg outside the allowlist, or "none", is rejected before any cryptographic operation runs.

func NewVerifier

func NewVerifier(allow []Algorithm, sel KeySelector) (Verifier, error)

NewVerifier builds a Verifier from an explicit algorithm allowlist and a KeySelector. It rejects an empty allowlist and any "none"/unsupported alg in the allowlist at construction time.

func NewVerifierKey

func NewVerifierKey(alg Algorithm, key VerifyingKey) (Verifier, error)

NewVerifierKey builds a Verifier with a single static key for one algorithm.

type VerifyingKey

type VerifyingKey interface {
	Algorithm() Algorithm
	// contains filtered or unexported methods
}

VerifyingKey is the per-algorithm verification carrier produced by RS256Verify/…/EdDSAVerify and by JWK.VerifyingKey. It reports the algorithm it can verify and checks a signature over a signing-input. It is the internal contract consumed by NewVerifier and JWKSClient.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL