MCPcopy Index your code
hub / github.com/auth0/go-jwt-middleware

github.com/auth0/go-jwt-middleware @v3.2.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v3.2.0 ↗ · + Follow
846 symbols 4,241 edges 87 files 441 documented · 52% 9 cross-repo links updated 4d agov3.2.0 · 2026-05-15★ 1,2052 open issues

Browse by type

Functions 744 Types & classes 102
What it actually does AI analysis from the code graph — generated when you open this
loading…
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
gRPC Interceptor → Core Engine → Validator

🚀 gRPC Support

Native gRPC integration with interceptors for unary and streaming methods:

import (
    jwtgrpc "github.com/auth0/go-jwt-middleware/v3/integrations/grpc"
    "github.com/auth0/go-jwt-middleware/v3/validator"
    "google.golang.org/grpc"
)

// Create validator (same as HTTP)
jwtValidator, _ := validator.New(
    validator.WithKeyFunc(keyFunc),
    validator.WithAlgorithm(validator.RS256),
    validator.WithIssuer("https://issuer.example.com/"),
    validator.WithAudience("my-api"),
)

// Create gRPC interceptor
interceptor, _ := jwtgrpc.New(
    jwtgrpc.WithValidator(jwtValidator),
    jwtgrpc.WithExcludedMethods("/health.Check"),
)

// Register with gRPC server
grpcServer := grpc.NewServer(
    grpc.UnaryInterceptor(interceptor.UnaryServerInterceptor()),
    grpc.StreamInterceptor(interceptor.StreamServerInterceptor()),
)

The gRPC interceptor also supports Multiple Custom Domains (MCD) with WithIssuers or WithIssuersResolver, and automatic JWKS routing via MultiIssuerProvider. See the gRPC integration docs for details.

🎁 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 realm="api", error="invalid_token", error_description="The access token is malformed"

{
  "error": "invalid_token",
  "error_description": "The access token is malformed",
  "error_code": "token_malformed"
}

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 *jwtmiddleware.ValidationError
    if errors.As(err, &validationErr) {
        switch validationErr.Code {
        case jwtmiddleware.ErrorCodeTokenExpired:
            http.Error(w, "Token expired", http.StatusUnauthorized)
        case jwtmiddleware.ErrorCodeInvalidIssuer:
            http.Error(w, "Untrusted issuer", http.StatusUnauthorized)
        case jwtmiddleware.ErrorCodeInvalidAudience:
            http.Error(w, "Audience mismatch", http.StatusUnauthorized)
        case jwtmiddleware.ErrorCodeInvalidSignature:
            http.Error(w, "Invalid signature", 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),
)

Error Responses

The default error handler returns specific HTTP status codes and structured JSON responses for each type of JWT validation failure:

Failure HTTP Status error error_code
Mal

Extension points exported contracts — how you extend this code

Core symbols most depended-on inside this repo

Shape

Function 559
Method 185
Struct 70
FuncType 13
Interface 11
TypeAlias 8

Languages

Go100%

Modules by API surface

core/dpop_test.go47 symbols
integrations/grpc/interceptor_test.go45 symbols
examples/http-dpop-example/main_integration_test.go37 symbols
examples/grpc-example/proto/greeter.pb.go32 symbols
core/dpop.go28 symbols
validator/validator_test.go27 symbols
option_test.go27 symbols
examples/http-dpop-required/main_integration_test.go27 symbols
integrations/grpc/error_handler_test.go21 symbols
examples/http-dpop-disabled/main_integration_test.go21 symbols
validator/dpop_test.go20 symbols
jwks/option.go20 symbols

For agents

$ claude mcp add go-jwt-middleware \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page