validator

package
v3.0.0 Latest Latest
Warning

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

Go to latest
Published: Jan 19, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package validator provides JWT validation using the lestrrat-go/jwx v3 library.

This package implements the ValidateToken interface required by the middleware and handles all aspects of JWT validation including signature verification, registered claims validation, and custom claims support.

Features

  • Signature verification using multiple algorithms (RS256, HS256, ES256, EdDSA, etc.)
  • Validation of registered claims (iss, aud, exp, nbf, iat)
  • Support for custom claims with validation logic
  • Clock skew tolerance for time-based claims
  • JWKS (JSON Web Key Set) support via key functions
  • Multiple issuer and audience support

Supported Algorithms

The validator supports 14 signature algorithms:

HMAC:

  • HS256, HS384, HS512

RSA:

  • RS256, RS384, RS512 (RSASSA-PKCS1-v1_5)
  • PS256, PS384, PS512 (RSASSA-PSS)

ECDSA:

  • ES256, ES384, ES512
  • ES256K (secp256k1 curve)

EdDSA:

  • EdDSA (Ed25519)

Basic Usage

import (
    "github.com/auth0/go-jwt-middleware/v3/validator"
    "github.com/auth0/go-jwt-middleware/v3/jwks"
)

issuerURL, _ := url.Parse("https://auth.example.com/")

// Create JWKS provider
provider, err := jwks.NewCachingProvider(
    jwks.WithIssuerURL(issuerURL),
)
if err != nil {
    log.Fatal(err)
}

// Create validator
v, err := validator.New(
    validator.WithKeyFunc(provider.KeyFunc),
    validator.WithAlgorithm(validator.RS256),
    validator.WithIssuer(issuerURL.String()),
    validator.WithAudience("my-api"),
)
if err != nil {
    log.Fatal(err)
}

// Validate token
claims, err := v.ValidateToken(ctx, tokenString)
if err != nil {
    // Token invalid
}

// Type assert to ValidatedClaims
validatedClaims := claims.(*validator.ValidatedClaims)

Custom Claims

Define custom claims by implementing the CustomClaims interface:

type MyCustomClaims struct {
    Scope       string   `json:"scope"`
    Permissions []string `json:"permissions"`
}

func (c *MyCustomClaims) Validate(ctx context.Context) error {
    if c.Scope == "" {
        return errors.New("scope is required")
    }
    return nil
}

// Use with validator
v, err := validator.New(
    validator.WithKeyFunc(keyFunc),
    validator.WithAlgorithm(validator.RS256),
    validator.WithIssuer("https://issuer.example.com/"),
    validator.WithAudience("my-api"),
    validator.WithCustomClaims(func() *MyCustomClaims {
        return &MyCustomClaims{}
    }),
)

// Access custom claims
claims, _ := v.ValidateToken(ctx, tokenString)
validatedClaims := claims.(*validator.ValidatedClaims)
customClaims := validatedClaims.CustomClaims.(*MyCustomClaims)
fmt.Println(customClaims.Scope)

Multiple Issuers and Audiences

Support tokens from multiple issuers or for multiple audiences:

v, err := validator.New(
    validator.WithKeyFunc(keyFunc),
    validator.WithAlgorithm(validator.RS256),
    validator.WithIssuers([]string{
        "https://auth1.example.com/",
        "https://auth2.example.com/",
    }),
    validator.WithAudiences([]string{
        "api1",
        "api2",
    }),
)

Clock Skew Tolerance

Allow time-based claims to be off by a certain duration:

v, err := validator.New(
    validator.WithKeyFunc(keyFunc),
    validator.WithAlgorithm(validator.RS256),
    validator.WithIssuer("https://issuer.example.com/"),
    validator.WithAudience("my-api"),
    validator.WithAllowedClockSkew(30*time.Second),
)

This is useful when server clocks are slightly out of sync. Default: 0 (no clock skew allowed)

Using HMAC Algorithms

For symmetric key algorithms (HS256, HS384, HS512):

secretKey := []byte("your-256-bit-secret")

keyFunc := func(ctx context.Context) (any, error) {
    return secretKey, nil
}

v, err := validator.New(
    validator.WithKeyFunc(keyFunc),
    validator.WithAlgorithm(validator.HS256),
    validator.WithIssuer("https://issuer.example.com/"),
    validator.WithAudience("my-api"),
)

Using RSA Public Keys

For asymmetric algorithms (RS256, PS256, ES256, etc.):

import (
    "crypto/rsa"
    "crypto/x509"
    "encoding/pem"
)

publicKeyPEM := []byte(`-----BEGIN PUBLIC KEY-----...`)

block, _ := pem.Decode(publicKeyPEM)
pubKey, _ := x509.ParsePKIXPublicKey(block.Bytes)
rsaPublicKey := pubKey.(*rsa.PublicKey)

keyFunc := func(ctx context.Context) (any, error) {
    return rsaPublicKey, nil
}

v, err := validator.New(
    validator.WithKeyFunc(keyFunc),
    validator.WithAlgorithm(validator.RS256),
    validator.WithIssuer("https://issuer.example.com/"),
    validator.WithAudience("my-api"),
)

Validated Claims Structure

The ValidatedClaims struct contains both registered and custom claims:

type ValidatedClaims struct {
    RegisteredClaims RegisteredClaims // Standard JWT claims
    CustomClaims     CustomClaims     // Your custom claims
}

type RegisteredClaims struct {
    Issuer    string   // iss
    Subject   string   // sub
    Audience  []string // aud
    ID        string   // jti
    Expiry    int64    // exp (Unix timestamp)
    NotBefore int64    // nbf (Unix timestamp)
    IssuedAt  int64    // iat (Unix timestamp)
}

Error Handling

claims, err := v.ValidateToken(ctx, tokenString)
if err != nil {
    // Token validation failed
    // Possible reasons:
    // - Invalid signature
    // - Token expired
    // - Token not yet valid
    // - Invalid issuer
    // - Invalid audience
    // - Custom claims validation failed
}

Performance

The validator is optimized for performance:

  • Single-pass claim extraction
  • Minimal memory allocations
  • Direct JWT payload decoding for custom claims
  • Efficient string comparison for issuer/audience

Typical validation time:

  • With JWKS cache hit: <1ms
  • With JWKS cache miss: 50-200ms (network fetch)
  • HMAC validation: <0.1ms
  • RSA validation: <0.5ms

Thread Safety

The Validator is immutable after creation and safe for concurrent use. The same Validator instance can be used to validate multiple tokens concurrently.

Migration from go-jose v2

This package uses lestrrat-go/jwx v3 instead of square/go-jose v2. Key differences:

1. Better performance and security 2. More comprehensive algorithm support 3. Improved JWKS handling with automatic kid matching 4. Native Go 1.18+ generics support 5. Active maintenance and updates

The API is designed to be familiar to go-jose users while leveraging the improvements in jwx v3.

Index

Constants

View Source
const (
	EdDSA  = SignatureAlgorithm("EdDSA")
	HS256  = SignatureAlgorithm("HS256")  // HMAC using SHA-256
	HS384  = SignatureAlgorithm("HS384")  // HMAC using SHA-384
	HS512  = SignatureAlgorithm("HS512")  // HMAC using SHA-512
	RS256  = SignatureAlgorithm("RS256")  // RSASSA-PKCS-v1.5 using SHA-256
	RS384  = SignatureAlgorithm("RS384")  // RSASSA-PKCS-v1.5 using SHA-384
	RS512  = SignatureAlgorithm("RS512")  // RSASSA-PKCS-v1.5 using SHA-512
	ES256  = SignatureAlgorithm("ES256")  // ECDSA using P-256 and SHA-256
	ES384  = SignatureAlgorithm("ES384")  // ECDSA using P-384 and SHA-384
	ES512  = SignatureAlgorithm("ES512")  // ECDSA using P-521 and SHA-512
	ES256K = SignatureAlgorithm("ES256K") // ECDSA using secp256k1 curve and SHA-256
	PS256  = SignatureAlgorithm("PS256")  // RSASSA-PSS using SHA256 and MGF1-SHA256
	PS384  = SignatureAlgorithm("PS384")  // RSASSA-PSS using SHA384 and MGF1-SHA384
	PS512  = SignatureAlgorithm("PS512")  // RSASSA-PSS using SHA512 and MGF1-SHA512
)

Signature algorithms

View Source
const DPoPSupportedAlgorithms = "ES256 ES384 ES512 RS256 RS384 RS512 PS256 PS384 PS512 EdDSA"

DPoPSupportedAlgorithms is a space-separated list of supported DPoP algorithms for use in WWW-Authenticate headers per RFC 9449 Section 7.1.

Variables

This section is empty.

Functions

This section is empty.

Types

type ConfirmationClaim

type ConfirmationClaim struct {
	// JKT is the JWK SHA-256 Thumbprint (base64url-encoded).
	// This thumbprint must match the JKT calculated from the DPoP proof's JWK.
	JKT string `json:"jkt"`
}

ConfirmationClaim represents the cnf (confirmation) claim per RFC 7800 and RFC 9449. It contains the JWK SHA-256 thumbprint that binds the access token to a specific key pair. This is used for DPoP (Demonstrating Proof-of-Possession) token binding.

type CustomClaims

type CustomClaims interface {
	Validate(context.Context) error
}

CustomClaims defines any custom data / claims wanted. The Validator will call the Validate function which is where custom validation logic can be defined.

type DPoPProofClaims

type DPoPProofClaims struct {
	// JTI is a unique identifier for the DPoP proof JWT.
	// Used for replay protection if nonce tracking is enabled.
	JTI string `json:"jti"`

	// HTM is the HTTP method (GET, POST, PUT, DELETE, etc.).
	// Must match the actual HTTP request method (case-sensitive).
	HTM string `json:"htm"`

	// HTU is the HTTP URI (full URL of the request).
	// Must match the actual request URL (scheme + host + path).
	HTU string `json:"htu"`

	// IAT is the time at which the DPoP proof was created (Unix timestamp).
	// Must be fresh (within configured offset and leeway).
	IAT int64 `json:"iat"`

	// Nonce is an optional server-provided nonce for replay protection.
	Nonce string `json:"nonce,omitempty"`

	// ATH is an optional access token hash (base64url-encoded SHA-256).
	// Used for additional binding in some implementations.
	ATH string `json:"ath,omitempty"`

	// PublicKey is the JWK extracted from the DPoP proof JWT header.
	// Used to verify the proof's signature.
	PublicKey any `json:"-"`

	// PublicKeyThumbprint is the JKT calculated from the PublicKey.
	// This is computed using SHA-256 thumbprint algorithm (RFC 7638).
	// Must match the cnf.jkt from the access token.
	PublicKeyThumbprint string `json:"-"`
}

DPoPProofClaims represents the claims in a DPoP proof JWT per RFC 9449. These claims are extracted from the JWT sent in the DPoP HTTP header.

func (*DPoPProofClaims) GetATH

func (d *DPoPProofClaims) GetATH() string

GetATH returns the access token hash (ath) from the DPoP proof. This is an optional claim that binds the proof to a specific access token. This method implements the core.DPoPProofClaims interface.

func (*DPoPProofClaims) GetHTM

func (d *DPoPProofClaims) GetHTM() string

GetHTM returns the HTTP method (htm) from the DPoP proof. This method implements the core.DPoPProofClaims interface.

func (*DPoPProofClaims) GetHTU

func (d *DPoPProofClaims) GetHTU() string

GetHTU returns the HTTP URI (htu) from the DPoP proof. This method implements the core.DPoPProofClaims interface.

func (*DPoPProofClaims) GetIAT

func (d *DPoPProofClaims) GetIAT() int64

GetIAT returns the issued-at timestamp (iat) from the DPoP proof. This method implements the core.DPoPProofClaims interface.

func (*DPoPProofClaims) GetJTI

func (d *DPoPProofClaims) GetJTI() string

GetJTI returns the unique identifier (jti) of the DPoP proof. This method implements the core.DPoPProofClaims interface.

func (*DPoPProofClaims) GetPublicKey

func (d *DPoPProofClaims) GetPublicKey() any

GetPublicKey returns the public key from the DPoP proof's JWK. This method implements the core.DPoPProofClaims interface.

func (*DPoPProofClaims) GetPublicKeyThumbprint

func (d *DPoPProofClaims) GetPublicKeyThumbprint() string

GetPublicKeyThumbprint returns the calculated JKT from the DPoP proof's JWK. This method implements the core.DPoPProofClaims interface.

type Option

type Option func(*Validator) error

Option is how options for the Validator are set up. Options return errors to enable validation during construction.

func WithAlgorithm

func WithAlgorithm(algorithm SignatureAlgorithm) Option

WithAlgorithm sets the signature algorithm that tokens must use. This is a required option.

Supported algorithms: RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512, HS256, HS384, HS512, EdDSA.

func WithAllowedClockSkew

func WithAllowedClockSkew(skew time.Duration) Option

WithAllowedClockSkew sets the allowed clock skew for time-based claims.

This allows for some tolerance when validating exp, nbf, and iat claims to account for clock differences between systems. If not set, the default is 0 (no clock skew allowed).

func WithAudience

func WithAudience(audience string) Option

WithAudience sets a single expected audience claim (aud) for token validation. This is a required option (use either WithAudience or WithAudiences, not both).

The audience should match one of the aud claims in the JWT. Tokens without a matching audience will be rejected.

func WithAudiences

func WithAudiences(audiences []string) Option

WithAudiences sets multiple expected audience claims (aud) for token validation. This is a required option (use either WithAudience or WithAudiences, not both).

The token must contain at least one of the specified audiences. Tokens without any matching audience will be rejected.

func WithCustomClaims

func WithCustomClaims[T CustomClaims](f func() T) Option

WithCustomClaims sets a function that returns a CustomClaims object for unmarshalling and validation.

The function is called during construction to validate it returns a non-nil value, and then called for each token validation to create a new instance.

Using generics allows you to return your concrete claims type directly without needing to explicitly cast to the CustomClaims interface.

IMPORTANT: The function must be:

  • Thread-safe (called concurrently by multiple requests)
  • Idempotent (returns a new instance each time, no shared state)
  • Fast (called on every token validation)
  • Panic-free (panics will crash the request handler)

Example:

validator.New(
    // ... other options
    validator.WithCustomClaims(func() *MyClaims {
        return &MyClaims{}  // No interface cast needed
    }),
)

func WithIssuer

func WithIssuer(issuerURL string) Option

WithIssuer sets a single expected issuer claim (iss) for token validation. This is a required option (use either WithIssuer or WithIssuers, not both).

The issuer URL should match the iss claim in the JWT. Tokens with a different issuer will be rejected.

func WithIssuers

func WithIssuers(issuers []string) Option

WithIssuers sets multiple expected issuer claims (iss) for token validation. This is a required option (use either WithIssuer or WithIssuers, not both).

The token must contain one of the specified issuers. Tokens without any matching issuer will be rejected.

func WithKeyFunc

func WithKeyFunc(keyFunc func(context.Context) (any, error)) Option

WithKeyFunc sets the function that provides the key for token verification. This is a required option.

The keyFunc is called during token validation to retrieve the key(s) used to verify the token signature. For JWKS-based validation, use jwks.Provider.KeyFunc.

type RegisteredClaims

type RegisteredClaims struct {
	Issuer    string   `json:"iss,omitempty"`
	Subject   string   `json:"sub,omitempty"`
	Audience  []string `json:"aud,omitempty"`
	Expiry    int64    `json:"exp,omitempty"`
	NotBefore int64    `json:"nbf,omitempty"`
	IssuedAt  int64    `json:"iat,omitempty"`
	ID        string   `json:"jti,omitempty"`
}

RegisteredClaims represents public claim values (as specified in RFC 7519).

type SignatureAlgorithm

type SignatureAlgorithm string

SignatureAlgorithm is a signature algorithm.

type ValidatedClaims

type ValidatedClaims struct {
	CustomClaims     CustomClaims
	RegisteredClaims RegisteredClaims

	// ConfirmationClaim contains the cnf claim for DPoP binding (RFC 7800, RFC 9449).
	// This field will be nil for Bearer tokens and populated for DPoP tokens.
	ConfirmationClaim *ConfirmationClaim `json:"cnf,omitempty"`
}

ValidatedClaims is the struct that will be inserted into the context for the user. CustomClaims will be nil unless WithCustomClaims is passed to New.

func (*ValidatedClaims) GetConfirmationJKT

func (v *ValidatedClaims) GetConfirmationJKT() string

GetConfirmationJKT returns the jkt from the cnf claim, or empty string if not present. This method implements the core.TokenClaims interface.

func (*ValidatedClaims) HasConfirmation

func (v *ValidatedClaims) HasConfirmation() bool

HasConfirmation returns true if the token has a cnf claim. This method implements the core.TokenClaims interface.

type Validator

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

Validator validates JWTs using the jwx v3 library.

func New

func New(opts ...Option) (*Validator, error)

New creates a new Validator with the provided options.

Required options:

  • WithKeyFunc: Function to provide verification key(s)
  • WithAlgorithm: Signature algorithm to validate
  • WithIssuer: Expected issuer claim (iss)
  • WithAudience or WithAudiences: Expected audience claim(s) (aud)

Optional options:

  • WithCustomClaims: Custom claims validation
  • WithAllowedClockSkew: Clock skew tolerance for time-based claims

Example:

validator, err := validator.New(
    validator.WithKeyFunc(keyFunc),
    validator.WithAlgorithm(validator.RS256),
    validator.WithIssuer("https://issuer.example.com/"),
    validator.WithAudience("my-api"),
    validator.WithAllowedClockSkew(30*time.Second),
)
if err != nil {
    log.Fatal(err)
}

func (*Validator) ValidateDPoPProof

func (v *Validator) ValidateDPoPProof(ctx context.Context, proofString string) (*DPoPProofClaims, error)

ValidateDPoPProof validates a DPoP proof JWT and returns the extracted claims. It verifies the JWT signature using the embedded JWK and calculates the JKT.

This method performs the following validations per RFC 9449:

  • Parses the DPoP proof JWT
  • Verifies the typ header is "dpop+jwt"
  • Extracts the JWK from the JWT header
  • Verifies the JWT signature using the embedded JWK
  • Extracts required claims (jti, htm, htu, iat)
  • Calculates the JKT (JWK thumbprint) using SHA-256

The method does NOT validate:

  • htm matches HTTP method (done in core)
  • htu matches request URL (done in core)
  • iat freshness (done in core)
  • JKT matches cnf.jkt from access token (done in core)

This separation ensures the validator remains a pure JWT validation library with no knowledge of HTTP requests or transport concerns.

func (*Validator) ValidateToken

func (v *Validator) ValidateToken(ctx context.Context, tokenString string) (any, error)

ValidateToken validates the passed in JWT. This method is optimized for performance and abstracts the underlying JWT library.

Jump to

Keyboard shortcuts

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