jwtmiddleware

package module
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: 9 Imported by: 0

README

Go JWT Middleware

GoDoc Go Report Card License Release Codecov Tests Ask DeepWiki

📚 Documentation • 🚀 Getting Started • ✨ What's New in v3 • 💬 Feedback

Documentation

  • Godoc - explore the go-jwt-middleware documentation.
  • Docs site — explore our docs site and learn more about Auth0.
  • Quickstart - our guide for adding go-jwt-middleware to your app.
  • Migration Guide - upgrading from v2 to v3.

What's New in v3

v3 introduces significant improvements while maintaining the simplicity and flexibility you expect:

🎯 Pure Options Pattern

All configuration through functional options for better IDE support and compile-time validation:

// v3: Clean, self-documenting API
validator.New(
    validator.WithKeyFunc(keyFunc),
    validator.WithAlgorithm(validator.RS256),
    validator.WithIssuer("https://issuer.example.com/"),
    validator.WithAudience("my-api"),
)

🔐 Enhanced JWT Library (lestrrat-go/jwx v3)

  • Better performance and security
  • Support for 14 signature algorithms (including EdDSA, ES256K)
  • Improved JWKS handling with automatic kid matching
  • Active maintenance and modern Go support

🏗️ Core-Adapter Architecture

Framework-agnostic validation logic that can be reused across HTTP, gRPC, and other transports:

HTTP Middleware → Core Engine → Validator

🎁 Type-Safe Claims with Generics

Use Go 1.24+ generics for compile-time type safety:

claims, err := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())

📊 Built-in Logging Support

Optional structured logging compatible with log/slog:

jwtmiddleware.New(
    jwtmiddleware.WithValidator(jwtValidator),
    jwtmiddleware.WithLogger(slog.Default()),
)

🛡️ Enhanced Security

  • RFC 6750 compliant error responses
  • Secure defaults (credentials required, clock skew = 0)
  • DPoP support (RFC 9449) for proof-of-possession tokens

🔑 DPoP (Demonstrating Proof-of-Possession)

Prevent token theft with proof-of-possession:

jwtmiddleware.New(
    jwtmiddleware.WithValidator(jwtValidator),
    jwtmiddleware.WithDPoPMode(jwtmiddleware.DPoPRequired),
)

Getting Started

Requirements

This library follows the same support policy as Go. The last two major Go releases are actively supported and compatibility issues will be fixed.

  • Go 1.24+

Installation

go get github.com/auth0/go-jwt-middleware/v3

Basic Usage

Simple Example with HMAC
package main

import (
	"context"
	"encoding/json"
	"log"
	"net/http"

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

var handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	// Type-safe claims retrieval with generics
	claims, err := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())
	if err != nil {
		http.Error(w, "failed to get claims", http.StatusInternalServerError)
		return
	}

	payload, err := json.Marshal(claims)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(payload)
})

func main() {
	keyFunc := func(ctx context.Context) (any, error) {
		// Our token must be signed using this secret
		return []byte("secret"), nil
	}

	// Create validator with options pattern
	jwtValidator, err := validator.New(
		validator.WithKeyFunc(keyFunc),
		validator.WithAlgorithm(validator.HS256),
		validator.WithIssuer("go-jwt-middleware-example"),
		validator.WithAudience("audience-example"),
	)
	if err != nil {
		log.Fatalf("failed to set up the validator: %v", err)
	}

	// Create middleware with options pattern
	middleware, err := jwtmiddleware.New(
		jwtmiddleware.WithValidator(jwtValidator),
	)
	if err != nil {
		log.Fatalf("failed to set up the middleware: %v", err)
	}

	http.ListenAndServe("0.0.0.0:3000", middleware.CheckJWT(handler))
}

Try it out:

curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnby1qd3QtbWlkZGxld2FyZS1leGFtcGxlIiwiYXVkIjoiYXVkaWVuY2UtZXhhbXBsZSIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsInVzZXJuYW1lIjoidXNlcjEyMyJ9.XFhrzWzntyINkgoRt2mb8dES84dJcuOoORdzKfwUX70" \
  http://localhost:3000

This JWT is signed with secret and contains:

{
  "iss": "go-jwt-middleware-example",
  "aud": "audience-example",
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "username": "user123"
}
Production Example with JWKS and Auth0
package main

import (
	"context"
	"log"
	"net/http"
	"net/url"
	"os"

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

func main() {
	issuerURL, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/")
	if err != nil {
		log.Fatalf("failed to parse issuer URL: %v", err)
	}

	// Create JWKS provider with caching
	provider, err := jwks.NewCachingProvider(
		jwks.WithIssuerURL(issuerURL),
	)
	if err != nil {
		log.Fatalf("failed to create JWKS provider: %v", err)
	}

	// Create validator
	jwtValidator, err := validator.New(
		validator.WithKeyFunc(provider.KeyFunc),
		validator.WithAlgorithm(validator.RS256),
		validator.WithIssuer(issuerURL.String()),
		validator.WithAudience(os.Getenv("AUTH0_AUDIENCE")),
	)
	if err != nil {
		log.Fatalf("failed to set up the validator: %v", err)
	}

	// Create middleware
	middleware, err := jwtmiddleware.New(
		jwtmiddleware.WithValidator(jwtValidator),
	)
	if err != nil {
		log.Fatalf("failed to set up the middleware: %v", err)
	}

	// Protected route
	http.Handle("/api/private", middleware.CheckJWT(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		claims, _ := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())
		w.Write([]byte("Hello, " + claims.RegisteredClaims.Subject))
	})))

	// Public route
	http.HandleFunc("/api/public", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello, anonymous user"))
	})

	log.Println("Server listening on :3000")
	http.ListenAndServe(":3000", nil)
}

Testing the Server

After running the server (go run main.go), test with curl:

Valid Token:

$ curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnby1qd3QtbWlkZGxld2FyZS1leGFtcGxlIiwiYXVkIjoiYXVkaWVuY2UtZXhhbXBsZSIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsInVzZXJuYW1lIjoidXNlcjEyMyJ9.XFhrzWzntyINkgoRt2mb8dES84dJcuOoORdzKfwUX70" localhost:3000

Response:

{
  "CustomClaims": null,
  "RegisteredClaims": {
    "iss": "go-jwt-middleware-example",
    "aud": ["audience-example"],
    "sub": "1234567890",
    "name": "John Doe",
    "iat": 1516239022
  }
}

Invalid Token:

$ curl -v -H "Authorization: Bearer invalid.token.here" localhost:3000

Response:

HTTP/1.1 401 Unauthorized
Content-Type: application/json
WWW-Authenticate: Bearer error="invalid_token", error_description="The access token is invalid"

{
  "error": "invalid_token",
  "error_description": "The access token is invalid"
}

Advanced Usage

Custom Claims

Define and validate custom claims:

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

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

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

// Access in handler
func handler(w http.ResponseWriter, r *http.Request) {
	claims, _ := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())
	customClaims := claims.CustomClaims.(*CustomClaims)

	if contains(customClaims.Permissions, "read:data") {
		// User has permission
	}
}

Optional Credentials

Allow both authenticated and public access:

middleware, err := jwtmiddleware.New(
	jwtmiddleware.WithValidator(jwtValidator),
	jwtmiddleware.WithCredentialsOptional(true),
)

func handler(w http.ResponseWriter, r *http.Request) {
	claims, err := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())
	if err != nil {
		// No JWT - serve public content
		w.Write([]byte("Public content"))
		return
	}
	// JWT present - serve authenticated content
	w.Write([]byte("Hello, " + claims.RegisteredClaims.Subject))
}

Custom Token Extraction

Extract tokens from cookies or query parameters:

// From cookie
middleware, err := jwtmiddleware.New(
	jwtmiddleware.WithValidator(jwtValidator),
	jwtmiddleware.WithTokenExtractor(jwtmiddleware.CookieTokenExtractor("jwt")),
)

// From query parameter
middleware, err := jwtmiddleware.New(
	jwtmiddleware.WithValidator(jwtValidator),
	jwtmiddleware.WithTokenExtractor(jwtmiddleware.ParameterTokenExtractor("token")),
)

// Try multiple sources
middleware, err := jwtmiddleware.New(
	jwtmiddleware.WithValidator(jwtValidator),
	jwtmiddleware.WithTokenExtractor(jwtmiddleware.MultiTokenExtractor(
		jwtmiddleware.AuthHeaderTokenExtractor,
		jwtmiddleware.CookieTokenExtractor("jwt"),
	)),
)

URL Exclusions

Skip JWT validation for specific URLs:

middleware, err := jwtmiddleware.New(
	jwtmiddleware.WithValidator(jwtValidator),
	jwtmiddleware.WithExclusionUrls([]string{
		"/health",
		"/metrics",
		"/public",
	}),
)

Structured Logging

Enable logging with log/slog or compatible loggers:

import "log/slog"

logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
	Level: slog.LevelDebug,
}))

middleware, err := jwtmiddleware.New(
	jwtmiddleware.WithValidator(jwtValidator),
	jwtmiddleware.WithLogger(logger),
)

Custom Error Handling

Implement custom error responses:

func customErrorHandler(w http.ResponseWriter, r *http.Request, err error) {
	log.Printf("JWT error: %v", err)

	if errors.Is(err, jwtmiddleware.ErrJWTMissing) {
		http.Error(w, "No token provided", http.StatusUnauthorized)
		return
	}

	var validationErr *core.ValidationError
	if errors.As(err, &validationErr) {
		switch validationErr.Code {
		case core.ErrorCodeTokenExpired:
			http.Error(w, "Token expired", http.StatusUnauthorized)
		default:
			http.Error(w, "Invalid token", http.StatusUnauthorized)
		}
		return
	}

	http.Error(w, "Unauthorized", http.StatusUnauthorized)
}

middleware, err := jwtmiddleware.New(
	jwtmiddleware.WithValidator(jwtValidator),
	jwtmiddleware.WithErrorHandler(customErrorHandler),
)

Clock Skew Tolerance

Allow for time drift between servers:

jwtValidator, 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),
)

DPoP (Demonstrating Proof-of-Possession)

v3 adds support for DPoP (RFC 9449), which provides proof-of-possession for access tokens. This prevents token theft and replay attacks.

DPoP Modes
Mode Description Use Case
DPoPAllowed (default) Accepts both Bearer and DPoP tokens Migration period, backward compatibility
DPoPRequired Only accepts DPoP tokens Maximum security
DPoPDisabled Ignores DPoP proofs, rejects DPoP scheme Legacy systems
Basic DPoP Setup
middleware, err := jwtmiddleware.New(
	jwtmiddleware.WithValidator(jwtValidator),
	jwtmiddleware.WithDPoPMode(jwtmiddleware.DPoPAllowed), // Default
)
Require DPoP for Maximum Security
middleware, err := jwtmiddleware.New(
	jwtmiddleware.WithValidator(jwtValidator),
	jwtmiddleware.WithDPoPMode(jwtmiddleware.DPoPRequired),
)
Behind a Proxy

When running behind a reverse proxy, configure trusted proxy headers:

middleware, err := jwtmiddleware.New(
	jwtmiddleware.WithValidator(jwtValidator),
	jwtmiddleware.WithDPoPMode(jwtmiddleware.DPoPRequired),
	jwtmiddleware.WithStandardProxy(),  // Trust X-Forwarded-* headers
)

See the DPoP examples for complete working code.

Examples

For complete working examples, check the examples directory:

Supported Algorithms

v3 supports 14 signature algorithms:

Type Algorithms
HMAC HS256, HS384, HS512
RSA RS256, RS384, RS512
RSA-PSS PS256, PS384, PS512
ECDSA ES256, ES384, ES512, ES256K
EdDSA EdDSA (Ed25519)

Migration from v2

See MIGRATION_GUIDE.md for a complete guide on upgrading from v2 to v3.

Key changes:

  • Pure options pattern for all components
  • Type-safe claims with generics
  • New JWT library (lestrrat-go/jwx v3)
  • Core-Adapter architecture

Feedback

Contributing

We appreciate feedback and contribution to this repo! Before you get started, please see the following:

Raise an issue

To provide feedback or report a bug, please raise an issue on our issue tracker.

Vulnerability Reporting

Please do not report security vulnerabilities on the public Github issue tracker. The Responsible Disclosure Program details the procedure for disclosing security issues.


Auth0 Logo

Auth0 is an easy to implement, adaptable authentication and authorization platform.
To learn more checkout Why Auth0?

This project is licensed under the MIT license. See the LICENSE file for more info.

Documentation

Overview

Package jwtmiddleware provides HTTP middleware for JWT authentication.

This package implements JWT authentication middleware for standard Go net/http servers. It validates JWTs, extracts claims, and makes them available in the request context. The middleware follows the Core-Adapter pattern, with this package serving as the HTTP transport adapter.

Quick Start

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

func main() {
    // Create JWKS provider
    issuerURL, _ := url.Parse("https://your-domain.auth0.com/")
    provider, err := jwks.NewCachingProvider(
        jwks.WithIssuerURL(issuerURL),
    )
    if err != nil {
        log.Fatal(err)
    }

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

// Create middleware
middleware, err := jwtmiddleware.New(
	jwtmiddleware.WithValidator(jwtValidator),
)
if err != nil {
	log.Fatal(err)
}	    // Use with your HTTP server
    http.Handle("/api/", middleware.CheckJWT(apiHandler))
    http.ListenAndServe(":8080", nil)
}

Accessing Claims

Use the type-safe generic helpers to access claims in your handlers:

func apiHandler(w http.ResponseWriter, r *http.Request) {
    // Type-safe claims retrieval
    claims, err := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())
    if err != nil {
        http.Error(w, "Unauthorized", http.StatusUnauthorized)
        return
    }

    // Access claims
    fmt.Fprintf(w, "Hello, %s!", claims.RegisteredClaims.Subject)
}

Alternative: Check if claims exist without retrieving them:

if jwtmiddleware.HasClaims(r.Context()) {
    // Claims are present
}

v2 compatibility (type assertion):

claimsValue := r.Context().Value(jwtmiddleware.ContextKey{})
if claimsValue == nil {
    // No claims
}
claims := claimsValue.(*validator.ValidatedClaims)

Configuration Options

All configuration is done through functional options:

Required:

  • WithValidator: A configured validator instance

Optional:

  • WithCredentialsOptional: Allow requests without JWT
  • WithValidateOnOptions: Validate JWT on OPTIONS requests
  • WithErrorHandler: Custom error response handler
  • WithTokenExtractor: Custom token extraction logic
  • WithExclusionUrls: URLs to skip JWT validation
  • WithLogger: Structured logging (compatible with log/slog)

Optional Credentials

Allow requests without JWT (useful for public + authenticated endpoints):

middleware, err := jwtmiddleware.New(
	jwtmiddleware.WithValidator(jwtValidator),
	jwtmiddleware.WithCredentialsOptional(true),
)	func handler(w http.ResponseWriter, r *http.Request) {
    claims, err := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())
    if err != nil {
        // No JWT provided - serve public content
        fmt.Fprintln(w, "Public content")
        return
    }
    // JWT provided - serve authenticated content
    fmt.Fprintf(w, "Hello, %s!", claims.RegisteredClaims.Subject)
}

Custom Error Handling

Implement custom error responses:

func myErrorHandler(w http.ResponseWriter, r *http.Request, err error) {
    log.Printf("JWT error: %v", err)

    // Check error type
    if errors.Is(err, jwtmiddleware.ErrJWTMissing) {
        http.Error(w, "No token provided", http.StatusUnauthorized)
        return
    }

    // Check for ValidationError
    var validationErr *core.ValidationError
    if errors.As(err, &validationErr) {
        switch validationErr.Code {
        case core.ErrorCodeTokenExpired:
            http.Error(w, "Token expired", http.StatusUnauthorized)
        default:
            http.Error(w, "Invalid token", http.StatusUnauthorized)
        }
        return
    }

    http.Error(w, "Unauthorized", http.StatusUnauthorized)
}

middleware, err := jwtmiddleware.New(
	jwtmiddleware.WithValidator(jwtValidator),
	jwtmiddleware.WithErrorHandler(myErrorHandler),
)# Token Extraction

Default: Authorization header with Bearer scheme

Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

Custom extractors:

From Cookie:

extractor := jwtmiddleware.CookieTokenExtractor("jwt")

From Query Parameter:

extractor := jwtmiddleware.ParameterTokenExtractor("token")

Multiple Sources (tries in order):

extractor := jwtmiddleware.MultiTokenExtractor(
    jwtmiddleware.AuthHeaderTokenExtractor,
    jwtmiddleware.CookieTokenExtractor("jwt"),
)

Use with middleware:

middleware, err := jwtmiddleware.New(
	jwtmiddleware.WithValidator(jwtValidator),
	jwtmiddleware.WithTokenExtractor(extractor),
)# URL Exclusions

Skip JWT validation for specific URLs:

middleware, err := jwtmiddleware.New(
	jwtmiddleware.WithValidator(jwtValidator),
	jwtmiddleware.WithExclusionUrls([]string{
	"/health",
	"/metrics",
	"/public",
}),
)# Logging

Enable structured logging (compatible with log/slog):

	import "log/slog"

	logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

	middleware, err := jwtmiddleware.New(
		jwtmiddleware.WithValidator(jwtValidator),
		jwtmiddleware.WithLogger(logger),
	)Logs will include:
  - Token extraction attempts
  - Validation success/failure with timing
  - Excluded URLs
  - OPTIONS request handling

Error Responses

The DefaultErrorHandler provides RFC 6750 compliant error responses:

401 Unauthorized (missing token):

{
    "error": "invalid_request",
    "error_description": "Authorization header required"
}
WWW-Authenticate: Bearer realm="api"

401 Unauthorized (invalid token):

{
    "error": "invalid_token",
    "error_description": "Token has expired",
    "error_code": "token_expired"
}
WWW-Authenticate: Bearer error="invalid_token", error_description="Token has expired"

400 Bad Request (extraction error):

{
    "error": "invalid_request",
    "error_description": "Authorization header format must be Bearer {token}"
}

Context Key

v3 uses an unexported context key for collision-free claims storage:

type contextKey int

This prevents conflicts with other packages. Always use the provided helper functions (GetClaims, HasClaims, SetClaims) to access claims.

v2 compatibility: The exported ContextKey{} struct is still available:

claimsValue := r.Context().Value(jwtmiddleware.ContextKey{})

However, the generic helpers are recommended for type safety.

Custom Claims

Define and use custom claims in your handlers:

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
}

Configure validator with custom claims:

jwtValidator, err := validator.New(
    validator.WithKeyFunc(provider.KeyFunc),
    validator.WithAlgorithm(validator.RS256),
    validator.WithIssuer(issuerURL.String()),
    validator.WithAudience("your-api-identifier"),
    validator.WithCustomClaims(func() *MyCustomClaims {
        return &MyCustomClaims{}
    }),
)

Access in handlers:

func handler(w http.ResponseWriter, r *http.Request) {
    claims, _ := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())
    customClaims := claims.CustomClaims.(*MyCustomClaims)

    if contains(customClaims.Permissions, "read:data") {
        // User has permission
    }
}

Thread Safety

The JWTMiddleware instance is immutable after creation and safe for concurrent use. The same middleware can be used across multiple routes and handle concurrent requests.

Performance

Typical request overhead with JWKS caching:

  • Token extraction: <0.1ms
  • Signature verification: <1ms (cached keys)
  • Claims validation: <0.1ms
  • Total: <2ms per request

First request (cold cache):

  • OIDC discovery: ~100-300ms
  • JWKS fetch: ~50-200ms
  • Validation: <1ms
  • Total: ~150-500ms

Architecture

This package is the HTTP adapter in the Core-Adapter pattern:

┌─────────────────────────────────────────────┐
│         HTTP Middleware (THIS PACKAGE)      │
│  - Token extraction from HTTP requests      │
│  - Error responses (401, 400)               │
│  - Context integration                      │
└────────────────┬────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────┐
│          Core Engine                        │
│  (Framework-Agnostic Validation Logic)      │
└────────────────┬────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────┐
│          Validator                          │
│  (JWT Parsing & Verification)               │
└─────────────────────────────────────────────┘

This design allows the same validation logic to be used with different transports (HTTP, gRPC, WebSocket, etc.) without code duplication.

Migration from v2

Key changes from v2 to v3:

1. Options Pattern: All configuration via functional options

// v2
jwtmiddleware.New(validator.New, options...)

// v3
jwtmiddleware.New(
	jwtmiddleware.WithValidator(validator),
	jwtmiddleware.WithCredentialsOptional(false),
)2. Generic Claims Retrieval: Type-safe with generics

// v2
claims := r.Context().Value(jwtmiddleware.ContextKey{}).(*validator.ValidatedClaims)

// v3
claims, err := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())

3. Validator Options: Pure options pattern

// v2
validator.New(keyFunc, alg, issuer, audience, opts...)

// v3
validator.New(
    validator.WithKeyFunc(keyFunc),
    validator.WithAlgorithm(validator.RS256),
    validator.WithIssuer(issuer),
    validator.WithAudience(audience),
)

4. JWKS Provider: Pure options pattern

// v2
jwks.NewProvider(issuerURL, options...)

// v3
jwks.NewCachingProvider(
    jwks.WithIssuerURL(issuerURL),
    jwks.WithCacheTTL(15*time.Minute),
)

5. ExclusionUrlHandler → ExclusionURLHandler: Proper URL capitalization

See MIGRATION.md for a complete guide.

Index

Constants

View Source
const (
	// AuthSchemeBearer represents Bearer token authorization.
	AuthSchemeBearer = core.AuthSchemeBearer
	// AuthSchemeDPoP represents DPoP token authorization.
	AuthSchemeDPoP = core.AuthSchemeDPoP
	// AuthSchemeUnknown represents an unknown or missing authorization scheme.
	AuthSchemeUnknown = core.AuthSchemeUnknown
)

Variables

View Source
var (
	// ErrJWTMissing is returned when the JWT is missing.
	// This is the same as core.ErrJWTMissing for consistency.
	ErrJWTMissing = core.ErrJWTMissing

	// ErrJWTInvalid is returned when the JWT is invalid.
	// This is the same as core.ErrJWTInvalid for consistency.
	ErrJWTInvalid = core.ErrJWTInvalid
)
View Source
var (
	ErrValidatorNil           = errors.New("validator cannot be nil (use WithValidator)")
	ErrErrorHandlerNil        = errors.New("errorHandler cannot be nil")
	ErrTokenExtractorNil      = errors.New("tokenExtractor cannot be nil")
	ErrExclusionUrlsEmpty     = errors.New("exclusion URLs list cannot be empty")
	ErrLoggerNil              = errors.New("logger cannot be nil")
	ErrDPoPHeaderExtractorNil = errors.New("DPoP header extractor cannot be nil")
)

Sentinel errors for configuration validation

Functions

func DPoPHeaderExtractor

func DPoPHeaderExtractor(r *http.Request) (string, error)

DPoPHeaderExtractor extracts the DPoP proof from the "DPoP" HTTP header. Returns empty string if the header is not present (which is valid for Bearer tokens). Returns an error if multiple DPoP headers are present (per RFC 9449).

func DefaultErrorHandler

func DefaultErrorHandler(w http.ResponseWriter, r *http.Request, err error)

DefaultErrorHandler is the default error handler implementation. It provides structured error responses with appropriate HTTP status codes and RFC 6750/RFC 9449 compliant WWW-Authenticate headers.

In DPoP allowed mode, both Bearer and DPoP challenges are returned per RFC 9449 Section 6.1.

func GetClaims

func GetClaims[T any](ctx context.Context) (T, error)

GetClaims retrieves claims from the context with type safety using generics. This provides compile-time type checking and eliminates the need for manual type assertions.

Example:

claims, err := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())
if err != nil {
    http.Error(w, "failed to get claims", http.StatusInternalServerError)
    return
}
fmt.Println(claims.RegisteredClaims.Subject)

func GetDPoPContext

func GetDPoPContext(ctx context.Context) *core.DPoPContext

GetDPoPContext retrieves the DPoP context from the request context. Returns nil if no DPoP context exists (e.g., for Bearer tokens).

This is a convenience wrapper around core.GetDPoPContext for use in HTTP handlers.

Example:

dpopCtx := jwtmiddleware.GetDPoPContext(r.Context())
if dpopCtx != nil {
    log.Printf("DPoP token from key: %s", dpopCtx.PublicKeyThumbprint)
}

func HasClaims

func HasClaims(ctx context.Context) bool

HasClaims checks if claims exist in the context.

Example:

if jwtmiddleware.HasClaims(r.Context()) {
    claims, _ := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())
    // Use claims...
}

func HasDPoPContext

func HasDPoPContext(ctx context.Context) bool

HasDPoPContext checks if a DPoP context exists in the request context. Returns true for DPoP-bound tokens, false for Bearer tokens.

This is a convenience wrapper around core.HasDPoPContext for use in HTTP handlers.

Example:

if jwtmiddleware.HasDPoPContext(r.Context()) {
    dpopCtx := jwtmiddleware.GetDPoPContext(r.Context())
    // Handle DPoP-specific logic...
}

func MustGetClaims

func MustGetClaims[T any](ctx context.Context) T

MustGetClaims retrieves claims from the context or panics. Use only when you are certain claims exist (e.g., after middleware has run).

Example:

claims := jwtmiddleware.MustGetClaims[*validator.ValidatedClaims](r.Context())
fmt.Println(claims.RegisteredClaims.Subject)

Types

type AuthScheme

type AuthScheme = core.AuthScheme

AuthScheme is an alias for core.AuthScheme for backward compatibility. New code should use core.AuthScheme directly.

type DPoPMode

type DPoPMode = core.DPoPMode

DPoPMode represents the operational mode for DPoP token validation.

const (
	// DPoPAllowed accepts both Bearer and DPoP tokens (default, non-breaking).
	// This mode allows gradual migration from Bearer to DPoP tokens.
	DPoPAllowed DPoPMode = core.DPoPAllowed

	// DPoPRequired only accepts DPoP tokens and rejects Bearer tokens.
	// Use this mode when all clients have been upgraded to support DPoP.
	DPoPRequired DPoPMode = core.DPoPRequired

	// DPoPDisabled only accepts Bearer tokens and ignores DPoP headers.
	// Use this mode to explicitly opt-out of DPoP support.
	DPoPDisabled DPoPMode = core.DPoPDisabled
)

type ErrorHandler

type ErrorHandler func(w http.ResponseWriter, r *http.Request, err error)

ErrorHandler is a handler which is called when an error occurs in the JWTMiddleware. The handler determines the HTTP response when a token is not found, is invalid, or other errors occur.

The default handler (DefaultErrorHandler) provides:

  • Structured JSON error responses with error codes
  • RFC 6750 compliant WWW-Authenticate headers (Bearer tokens)
  • Appropriate HTTP status codes based on error type
  • Security-conscious error messages (no sensitive details by default)
  • Extensible architecture for future authentication schemes (e.g., DPoP per RFC 9449)

Custom error handlers should check for ErrJWTMissing and ErrJWTInvalid sentinel errors, as well as core.ValidationError for detailed error codes.

Future extensions (e.g., DPoP support) can use the same pattern:

  • Add DPoP-specific error codes to core.ValidationError
  • Update mapValidationError to handle DPoP errors
  • Return appropriate WWW-Authenticate headers with DPoP scheme

type ErrorResponse

type ErrorResponse struct {
	// Error is the main error message
	Error string `json:"error"`

	// ErrorDescription provides additional context (optional)
	ErrorDescription string `json:"error_description,omitempty"`

	// ErrorCode is a machine-readable error code (optional)
	ErrorCode string `json:"error_code,omitempty"`
}

ErrorResponse represents a structured error response.

type ExclusionURLHandler

type ExclusionURLHandler func(r *http.Request) bool

ExclusionURLHandler is a function that takes in a http.Request and returns true if the request should be excluded from JWT validation.

type ExtractedToken

type ExtractedToken struct {
	Token  string
	Scheme AuthScheme
}

ExtractedToken holds both the extracted token and the authorization scheme used. This allows the middleware to enforce that DPoP scheme requires a DPoP proof.

func AuthHeaderTokenExtractor

func AuthHeaderTokenExtractor(r *http.Request) (ExtractedToken, error)

AuthHeaderTokenExtractor is a TokenExtractor that takes a request and extracts the token and scheme from the Authorization header. Supports both "Bearer" and "DPoP" authorization schemes.

Security: Rejects requests with multiple Authorization headers per RFC 9449.

type JWTMiddleware

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

JWTMiddleware is a middleware that validates JWTs and makes claims available in the request context. It wraps the core validation engine and provides HTTP-specific functionality like token extraction and error handling.

Claims are stored in the context using core.SetClaims() and can be retrieved using core.GetClaims[T]().

func New

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

New constructs a new JWTMiddleware instance with the supplied options. All parameters are passed via options (pure options pattern).

Required options:

  • WithValidator: A configured validator instance

Example:

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

middleware, err := jwtmiddleware.New(
    jwtmiddleware.WithValidator(v),
    jwtmiddleware.WithCredentialsOptional(false),
)
if err != nil {
    log.Fatalf("failed to create middleware: %v", err)
}

func (*JWTMiddleware) CheckJWT

func (m *JWTMiddleware) CheckJWT(next http.Handler) http.Handler

CheckJWT is the main JWTMiddleware function which performs the main logic. It is passed a http.Handler which will be called if the JWT passes validation.

type Logger

type Logger interface {
	Debug(msg string, args ...any)
	Info(msg string, args ...any)
	Warn(msg string, args ...any)
	Error(msg string, args ...any)
}

Logger defines an optional logging interface compatible with log/slog. This is the same interface used by core for consistent logging across the stack.

type Option

type Option func(*JWTMiddleware) error

Option configures the JWTMiddleware. Returns error for validation failures.

func WithAPIGatewayProxy

func WithAPIGatewayProxy() Option

WithAPIGatewayProxy configures trust for API gateways (AWS API Gateway, Kong, Traefik). Trusts X-Forwarded-Proto, X-Forwarded-Host, and X-Forwarded-Prefix headers. Use this when your gateway adds path prefixes (e.g., /api/v1).

This is a convenience function equivalent to:

WithTrustedProxies(&TrustedProxyConfig{
    TrustXForwardedProto:  true,
    TrustXForwardedHost:   true,
    TrustXForwardedPrefix: true,
})

Example:

middleware, err := jwtmiddleware.New(
    jwtmiddleware.WithValidator(validator),
    jwtmiddleware.WithAPIGatewayProxy(),
)

func WithCredentialsOptional

func WithCredentialsOptional(value bool) Option

WithCredentialsOptional sets whether credentials are optional. If set to true, an empty token will be considered valid.

Default: false (credentials required)

func WithDPoPHeaderExtractor

func WithDPoPHeaderExtractor(extractor func(*http.Request) (string, error)) Option

WithDPoPHeaderExtractor sets a custom DPoP header extractor. Optional - defaults to extracting from the "DPoP" HTTP header per RFC 9449.

Use this for non-standard scenarios:

  • Custom header names (e.g., "X-DPoP-Proof")
  • Header transformations (e.g., base64 decoding)
  • Alternative sources (e.g., query parameters)
  • Testing/mocking

Example (custom header name):

middleware, err := jwtmiddleware.New(
    jwtmiddleware.WithValidator(validator),
    jwtmiddleware.WithDPoPHeaderExtractor(func(r *http.Request) (string, error) {
        return r.Header.Get("X-DPoP-Proof"), nil
    }),
)

func WithDPoPIATLeeway

func WithDPoPIATLeeway(leeway time.Duration) Option

WithDPoPIATLeeway sets the clock skew allowance for DPoP proof iat claims. This allows DPoP proofs with iat timestamps slightly in the future due to clock drift.

Default: 5 seconds

Example:

middleware, err := jwtmiddleware.New(
    jwtmiddleware.WithValidator(validator),
    jwtmiddleware.WithDPoPIATLeeway(30 * time.Second), // More lenient: 30s
)

func WithDPoPMode

func WithDPoPMode(mode core.DPoPMode) Option

WithDPoPMode sets the DPoP operational mode.

Modes:

  • core.DPoPAllowed (default): Accept both Bearer and DPoP tokens
  • core.DPoPRequired: Only accept DPoP tokens, reject Bearer tokens
  • core.DPoPDisabled: Only accept Bearer tokens, ignore DPoP headers

Example:

middleware, err := jwtmiddleware.New(
    jwtmiddleware.WithValidator(validator),
    jwtmiddleware.WithDPoPMode(core.DPoPRequired), // Require DPoP
)

func WithDPoPProofOffset

func WithDPoPProofOffset(offset time.Duration) Option

WithDPoPProofOffset sets the maximum age for DPoP proofs. This determines how far in the past a DPoP proof's iat timestamp can be.

Default: 300 seconds (5 minutes)

Example:

middleware, err := jwtmiddleware.New(
    jwtmiddleware.WithValidator(validator),
    jwtmiddleware.WithDPoPProofOffset(60 * time.Second), // Stricter: 60s
)

func WithErrorHandler

func WithErrorHandler(h ErrorHandler) Option

WithErrorHandler sets the handler called when errors occur during JWT validation. See the ErrorHandler type for more information.

Default: DefaultErrorHandler

func WithExclusionUrls

func WithExclusionUrls(exclusions []string) Option

WithExclusionUrls configures URL patterns to exclude from JWT validation. URLs can be full URLs or just paths.

func WithLogger

func WithLogger(logger Logger) Option

WithLogger sets an optional logger for the middleware. The logger will be used throughout the validation flow in both middleware and core.

The logger interface is compatible with log/slog.Logger and similar loggers.

Example:

middleware, err := jwtmiddleware.New(
    jwtmiddleware.WithValidateToken(validator.ValidateToken),
    jwtmiddleware.WithLogger(slog.Default()),
)

func WithRFC7239Proxy

func WithRFC7239Proxy() Option

WithRFC7239Proxy configures trust for RFC 7239 Forwarded header. This is the most secure option if your proxy supports the structured Forwarded header.

This is a convenience function equivalent to:

WithTrustedProxies(&TrustedProxyConfig{
    TrustForwarded: true,
})

Example:

middleware, err := jwtmiddleware.New(
    jwtmiddleware.WithValidator(validator),
    jwtmiddleware.WithRFC7239Proxy(),
)

func WithStandardProxy

func WithStandardProxy() Option

WithStandardProxy configures trust for standard reverse proxies (Nginx, Apache, HAProxy). Trusts X-Forwarded-Proto and X-Forwarded-Host headers. Use this for typical web server deployments behind a reverse proxy.

This is a convenience function equivalent to:

WithTrustedProxies(&TrustedProxyConfig{
    TrustXForwardedProto: true,
    TrustXForwardedHost:  true,
})

Example:

middleware, err := jwtmiddleware.New(
    jwtmiddleware.WithValidator(validator),
    jwtmiddleware.WithStandardProxy(),
)

func WithTokenExtractor

func WithTokenExtractor(e TokenExtractor) Option

WithTokenExtractor sets the function to extract the JWT from the request.

Default: AuthHeaderTokenExtractor

func WithTrustedProxies

func WithTrustedProxies(config *TrustedProxyConfig) Option

WithTrustedProxies configures trusted proxy headers for URL reconstruction. Required when behind reverse proxies to correctly validate DPoP HTU claim.

SECURITY WARNING: Only use when your application is behind a trusted reverse proxy that strips client-provided forwarded headers. DO NOT use for direct internet-facing deployments.

Example:

middleware, err := jwtmiddleware.New(
    jwtmiddleware.WithValidator(validator),
    jwtmiddleware.WithTrustedProxies(&jwtmiddleware.TrustedProxyConfig{
        TrustXForwardedProto: true,
        TrustXForwardedHost:  true,
    }),
)

func WithValidateOnOptions

func WithValidateOnOptions(value bool) Option

WithValidateOnOptions sets whether OPTIONS requests should have their JWT validated.

Default: true (OPTIONS requests are validated)

func WithValidator

func WithValidator(v *validator.Validator) Option

WithValidator configures the middleware with a JWT validator. This is the REQUIRED way to configure the middleware.

The validator must implement ValidateToken, and optionally ValidateDPoPProof for DPoP support. The Auth0 validator package provides both methods automatically.

Example:

validator, _ := validator.New(...)  // Supports both JWT and DPoP
middleware, err := jwtmiddleware.New(
    jwtmiddleware.WithValidator(validator),
)

type TokenExtractor

type TokenExtractor func(r *http.Request) (ExtractedToken, error)

TokenExtractor is a function that takes a request as input and returns an ExtractedToken containing both the token and its authorization scheme, or an error. An error should only be returned if an attempt to specify a token was found, but the information was somehow incorrectly formed. In the case where a token is simply not present, this should not be treated as an error. An empty ExtractedToken should be returned in that case.

For extractors that don't have scheme information (cookies, query params), the Scheme field should be set to AuthSchemeUnknown.

func CookieTokenExtractor

func CookieTokenExtractor(cookieName string) TokenExtractor

CookieTokenExtractor builds a TokenExtractor that takes a request and extracts the token from the cookie using the passed in cookieName. Note: Cookies do not carry scheme information, so Scheme will be AuthSchemeUnknown.

func MultiTokenExtractor

func MultiTokenExtractor(extractors ...TokenExtractor) TokenExtractor

MultiTokenExtractor returns a TokenExtractor that runs multiple TokenExtractors and takes the one that does not return an empty token. If a TokenExtractor returns an error that error is immediately returned.

func ParameterTokenExtractor

func ParameterTokenExtractor(param string) TokenExtractor

ParameterTokenExtractor returns a TokenExtractor that extracts the token from the specified query string parameter. Note: Query parameters do not carry scheme information, so Scheme will be AuthSchemeUnknown.

type TrustedProxyConfig

type TrustedProxyConfig struct {
	// TrustXForwardedProto enables X-Forwarded-Proto header (https/http scheme)
	TrustXForwardedProto bool

	// TrustXForwardedHost enables X-Forwarded-Host header (original hostname)
	TrustXForwardedHost bool

	// TrustXForwardedPrefix enables X-Forwarded-Prefix header (API gateway path prefix)
	TrustXForwardedPrefix bool

	// TrustForwarded enables RFC 7239 Forwarded header (most secure, structured format)
	TrustForwarded bool
}

TrustedProxyConfig defines which reverse proxy headers to trust.

SECURITY WARNING: Only enable when behind a trusted reverse proxy! Enabling this in direct internet-facing deployments allows header injection attacks.

When enabled, the middleware will trust forwarded headers (X-Forwarded-*, Forwarded) to reconstruct the original client request URL for DPoP HTU validation.

Design decisions and considerations: - Secure by default: nil config means NO headers are trusted - Explicit opt-in required for each header type - RFC 7239 Forwarded takes precedence over X-Forwarded-* when both are enabled - Leftmost value used for multi-proxy chains (closest to client) - Empty or malformed headers are safely ignored (falls back to direct request)

Known limitations: - Headers are assumed to be properly sanitized by the reverse proxy - No validation of header value formats (relies on reverse proxy to provide valid values) - Port numbers are stripped from host for HTU validation (per DPoP spec)

Future considerations: - Configurable header value length limits - Support for custom/non-standard forwarded headers

Directories

Path Synopsis
Package core provides framework-agnostic JWT validation logic that can be used across different transport layers (HTTP, gRPC, etc.).
Package core provides framework-agnostic JWT validation logic that can be used across different transport layers (HTTP, gRPC, etc.).
internal
oidc
Package oidc provides OIDC (OpenID Connect) discovery functionality.
Package oidc provides OIDC (OpenID Connect) discovery functionality.
Package jwks provides JWKS (JSON Web Key Set) fetching and caching for JWT validation.
Package jwks provides JWKS (JSON Web Key Set) fetching and caching for JWT validation.
Package validator provides JWT validation using the lestrrat-go/jwx v3 library.
Package validator provides JWT validation using the lestrrat-go/jwx v3 library.

Jump to

Keyboard shortcuts

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