{"id":13836853,"url":"https://github.com/auth0/go-jwt-middleware","last_synced_at":"2026-04-09T12:20:55.220Z","repository":{"id":39648863,"uuid":"27879069","full_name":"auth0/go-jwt-middleware","owner":"auth0","description":"A Middleware for Go Programming Language to check for JWTs on HTTP requests","archived":false,"fork":false,"pushed_at":"2025-04-16T23:02:37.000Z","size":462,"stargazers_count":1121,"open_issues_count":12,"forks_count":208,"subscribers_count":114,"default_branch":"master","last_synced_at":"2025-04-30T12:17:27.293Z","etag":null,"topics":["dx-sdk"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/auth0.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2014-12-11T15:56:46.000Z","updated_at":"2025-04-30T05:36:04.000Z","dependencies_parsed_at":"2023-10-11T18:26:11.697Z","dependency_job_id":"bdcc6440-c97a-4412-8671-b7db364edc0f","html_url":"https://github.com/auth0/go-jwt-middleware","commit_stats":{"total_commits":210,"total_committers":38,"mean_commits":5.526315789473684,"dds":0.7238095238095238,"last_synced_commit":"b4b1b5f6d1b1eb3c7f4538a29f2caf2889693619"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/auth0%2Fgo-jwt-middleware","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/auth0%2Fgo-jwt-middleware/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/auth0%2Fgo-jwt-middleware/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/auth0%2Fgo-jwt-middleware/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/auth0","download_url":"https://codeload.github.com/auth0/go-jwt-middleware/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254040167,"owners_count":22004459,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dx-sdk"],"created_at":"2024-08-04T15:00:55.747Z","updated_at":"2026-04-09T12:20:55.203Z","avatar_url":"https://github.com/auth0.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"![Go JWT Middleware](https://cdn.auth0.com/website/sdks/banners/go-jwt-middleware.png)\n\n\u003cdiv align=\"center\"\u003e\n\n[![GoDoc](https://pkg.go.dev/badge/github.com/auth0/go-jwt-middleware.svg)](https://pkg.go.dev/github.com/auth0/go-jwt-middleware/v3)\n[![Go Report Card](https://goreportcard.com/badge/github.com/auth0/go-jwt-middleware/v3?style=flat-square)](https://goreportcard.com/report/github.com/auth0/go-jwt-middleware/v3)\n[![License](https://img.shields.io/github/license/auth0/go-jwt-middleware.svg?logo=fossa\u0026style=flat-square)](https://github.com/auth0/go-jwt-middleware/blob/master/LICENSE)\n[![Release](https://img.shields.io/github/v/release/auth0/go-jwt-middleware?include_prereleases\u0026style=flat-square)](https://github.com/auth0/go-jwt-middleware/releases)\n[![Codecov](https://img.shields.io/codecov/c/github/auth0/go-jwt-middleware?logo=codecov\u0026style=flat-square\u0026token=fs2WrOXe9H)](https://codecov.io/gh/auth0/go-jwt-middleware)\n[![Tests](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fauth0%2Fgo-jwt-middleware%2Fbadge%3Fref%3Dmaster\u0026style=flat-square)](https://github.com/auth0/go-jwt-middleware/actions?query=branch%3Amaster)\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/auth0/go-jwt-middleware)\n\n📚 [Documentation](#documentation) • 🚀 [Getting Started](#getting-started) • ✨ [What's New in v3](#whats-new-in-v3) • 💬 [Feedback](#feedback)\n\u003c/div\u003e\n\n## Documentation\n\n- [Godoc](https://pkg.go.dev/github.com/auth0/go-jwt-middleware/v3) - explore the go-jwt-middleware documentation.\n- [Docs site](https://www.auth0.com/docs) — explore our docs site and learn more about Auth0.\n- [Quickstart](https://auth0.com/docs/quickstart/backend/golang/interactive) - our guide for adding go-jwt-middleware to your app.\n- [Migration Guide](./MIGRATION_GUIDE.md) - upgrading from v2 to v3.\n\n## What's New in v3\n\nv3 introduces significant improvements while maintaining the simplicity and flexibility you expect:\n\n### 🎯 Pure Options Pattern\nAll configuration through functional options for better IDE support and compile-time validation:\n\n```go\n// v3: Clean, self-documenting API\nvalidator.New(\n    validator.WithKeyFunc(keyFunc),\n    validator.WithAlgorithm(validator.RS256),\n    validator.WithIssuer(\"https://issuer.example.com/\"),\n    validator.WithAudience(\"my-api\"),\n)\n```\n\n### 🔐 Enhanced JWT Library (lestrrat-go/jwx v3)\n- Better performance and security\n- Support for 14 signature algorithms (including EdDSA, ES256K)\n- Improved JWKS handling with automatic `kid` matching\n- Active maintenance and modern Go support\n\n### 🏗️ Core-Adapter Architecture\nFramework-agnostic validation logic that can be reused across HTTP, gRPC, and other transports:\n\n```\nHTTP Middleware → Core Engine → Validator\n```\n\n### 🎁 Type-Safe Claims with Generics\nUse Go 1.24+ generics for compile-time type safety:\n\n```go\nclaims, err := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())\n```\n\n### 📊 Built-in Logging Support\nOptional structured logging compatible with `log/slog`:\n\n```go\njwtmiddleware.New(\n    jwtmiddleware.WithValidator(jwtValidator),\n    jwtmiddleware.WithLogger(slog.Default()),\n)\n```\n\n### 🛡️ Enhanced Security\n- RFC 6750 compliant error responses\n- Secure defaults (credentials required, clock skew = 0)\n- **DPoP support** (RFC 9449) for proof-of-possession tokens\n\n### 🔑 DPoP (Demonstrating Proof-of-Possession)\nPrevent token theft with proof-of-possession:\n\n```go\njwtmiddleware.New(\n    jwtmiddleware.WithValidator(jwtValidator),\n    jwtmiddleware.WithDPoPMode(jwtmiddleware.DPoPRequired),\n)\n```\n\n## Getting Started\n\n### Requirements\n\nThis library follows the [same support policy as Go](https://go.dev/doc/devel/release#policy). The last two major Go releases are actively supported and compatibility issues will be fixed.\n\n- **Go 1.24+**\n\n### Installation\n\n```shell\ngo get github.com/auth0/go-jwt-middleware/v3\n```\n\n### Basic Usage\n\n#### Simple Example with HMAC\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/auth0/go-jwt-middleware/v3\"\n\t\"github.com/auth0/go-jwt-middleware/v3/validator\"\n)\n\nvar handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t// Type-safe claims retrieval with generics\n\tclaims, err := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())\n\tif err != nil {\n\t\thttp.Error(w, \"failed to get claims\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tpayload, err := json.Marshal(claims)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.Write(payload)\n})\n\nfunc main() {\n\tkeyFunc := func(ctx context.Context) (any, error) {\n\t\t// Our token must be signed using this secret\n\t\treturn []byte(\"secret\"), nil\n\t}\n\n\t// Create validator with options pattern\n\tjwtValidator, err := validator.New(\n\t\tvalidator.WithKeyFunc(keyFunc),\n\t\tvalidator.WithAlgorithm(validator.HS256),\n\t\tvalidator.WithIssuer(\"go-jwt-middleware-example\"),\n\t\tvalidator.WithAudience(\"audience-example\"),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to set up the validator: %v\", err)\n\t}\n\n\t// Create middleware with options pattern\n\tmiddleware, err := jwtmiddleware.New(\n\t\tjwtmiddleware.WithValidator(jwtValidator),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to set up the middleware: %v\", err)\n\t}\n\n\thttp.ListenAndServe(\"0.0.0.0:3000\", middleware.CheckJWT(handler))\n}\n```\n\n**Try it out:**\n```bash\ncurl -H \"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnby1qd3QtbWlkZGxld2FyZS1leGFtcGxlIiwiYXVkIjoiYXVkaWVuY2UtZXhhbXBsZSIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsInVzZXJuYW1lIjoidXNlcjEyMyJ9.XFhrzWzntyINkgoRt2mb8dES84dJcuOoORdzKfwUX70\" \\\n  http://localhost:3000\n```\n\nThis JWT is signed with `secret` and contains:\n```json\n{\n  \"iss\": \"go-jwt-middleware-example\",\n  \"aud\": \"audience-example\",\n  \"sub\": \"1234567890\",\n  \"name\": \"John Doe\",\n  \"iat\": 1516239022,\n  \"username\": \"user123\"\n}\n```\n\n#### Production Example with JWKS and Auth0\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\n\t\"github.com/auth0/go-jwt-middleware/v3\"\n\t\"github.com/auth0/go-jwt-middleware/v3/jwks\"\n\t\"github.com/auth0/go-jwt-middleware/v3/validator\"\n)\n\nfunc main() {\n\tissuerURL, err := url.Parse(\"https://\" + os.Getenv(\"AUTH0_DOMAIN\") + \"/\")\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to parse issuer URL: %v\", err)\n\t}\n\n\t// Create JWKS provider with caching\n\tprovider, err := jwks.NewCachingProvider(\n\t\tjwks.WithIssuerURL(issuerURL),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to create JWKS provider: %v\", err)\n\t}\n\n\t// Create validator\n\tjwtValidator, err := validator.New(\n\t\tvalidator.WithKeyFunc(provider.KeyFunc),\n\t\tvalidator.WithAlgorithm(validator.RS256),\n\t\tvalidator.WithIssuer(issuerURL.String()),\n\t\tvalidator.WithAudience(os.Getenv(\"AUTH0_AUDIENCE\")),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to set up the validator: %v\", err)\n\t}\n\n\t// Create middleware\n\tmiddleware, err := jwtmiddleware.New(\n\t\tjwtmiddleware.WithValidator(jwtValidator),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to set up the middleware: %v\", err)\n\t}\n\n\t// Protected route\n\thttp.Handle(\"/api/private\", middleware.CheckJWT(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tclaims, _ := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())\n\t\tw.Write([]byte(\"Hello, \" + claims.RegisteredClaims.Subject))\n\t})))\n\n\t// Public route\n\thttp.HandleFunc(\"/api/public\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"Hello, anonymous user\"))\n\t})\n\n\tlog.Println(\"Server listening on :3000\")\n\thttp.ListenAndServe(\":3000\", nil)\n}\n```\n\n### Testing the Server\n\nAfter running the server (`go run main.go`), test with curl:\n\n**Valid Token:**\n```bash\n$ curl -H \"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnby1qd3QtbWlkZGxld2FyZS1leGFtcGxlIiwiYXVkIjoiYXVkaWVuY2UtZXhhbXBsZSIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsInVzZXJuYW1lIjoidXNlcjEyMyJ9.XFhrzWzntyINkgoRt2mb8dES84dJcuOoORdzKfwUX70\" localhost:3000\n```\n\nResponse:\n```json\n{\n  \"CustomClaims\": null,\n  \"RegisteredClaims\": {\n    \"iss\": \"go-jwt-middleware-example\",\n    \"aud\": [\"audience-example\"],\n    \"sub\": \"1234567890\",\n    \"name\": \"John Doe\",\n    \"iat\": 1516239022\n  }\n}\n```\n\n**Invalid Token:**\n```bash\n$ curl -v -H \"Authorization: Bearer invalid.token.here\" localhost:3000\n```\n\nResponse:\n```\nHTTP/1.1 401 Unauthorized\nContent-Type: application/json\nWWW-Authenticate: Bearer realm=\"api\", error=\"invalid_token\", error_description=\"The access token is malformed\"\n\n{\n  \"error\": \"invalid_token\",\n  \"error_description\": \"The access token is malformed\",\n  \"error_code\": \"token_malformed\"\n}\n```\n\n## Advanced Usage\n\n### Custom Claims\n\nDefine and validate custom claims:\n\n```go\ntype CustomClaims struct {\n\tScope       string   `json:\"scope\"`\n\tPermissions []string `json:\"permissions\"`\n}\n\nfunc (c *CustomClaims) Validate(ctx context.Context) error {\n\tif c.Scope == \"\" {\n\t\treturn errors.New(\"scope is required\")\n\t}\n\treturn nil\n}\n\n// Use with validator\njwtValidator, err := validator.New(\n\tvalidator.WithKeyFunc(keyFunc),\n\tvalidator.WithAlgorithm(validator.RS256),\n\tvalidator.WithIssuer(\"https://issuer.example.com/\"),\n\tvalidator.WithAudience(\"my-api\"),\n\tvalidator.WithCustomClaims(func() *CustomClaims {\n\t\treturn \u0026CustomClaims{}\n\t}),\n)\n\n// Access in handler\nfunc handler(w http.ResponseWriter, r *http.Request) {\n\tclaims, _ := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())\n\tcustomClaims := claims.CustomClaims.(*CustomClaims)\n\n\tif contains(customClaims.Permissions, \"read:data\") {\n\t\t// User has permission\n\t}\n}\n```\n\n### Optional Credentials\n\nAllow both authenticated and public access:\n\n```go\nmiddleware, err := jwtmiddleware.New(\n\tjwtmiddleware.WithValidator(jwtValidator),\n\tjwtmiddleware.WithCredentialsOptional(true),\n)\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n\tclaims, err := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())\n\tif err != nil {\n\t\t// No JWT - serve public content\n\t\tw.Write([]byte(\"Public content\"))\n\t\treturn\n\t}\n\t// JWT present - serve authenticated content\n\tw.Write([]byte(\"Hello, \" + claims.RegisteredClaims.Subject))\n}\n```\n\n### Custom Token Extraction\n\nExtract tokens from cookies or query parameters:\n\n```go\n// From cookie\nmiddleware, err := jwtmiddleware.New(\n\tjwtmiddleware.WithValidator(jwtValidator),\n\tjwtmiddleware.WithTokenExtractor(jwtmiddleware.CookieTokenExtractor(\"jwt\")),\n)\n\n// From query parameter\nmiddleware, err := jwtmiddleware.New(\n\tjwtmiddleware.WithValidator(jwtValidator),\n\tjwtmiddleware.WithTokenExtractor(jwtmiddleware.ParameterTokenExtractor(\"token\")),\n)\n\n// Try multiple sources\nmiddleware, err := jwtmiddleware.New(\n\tjwtmiddleware.WithValidator(jwtValidator),\n\tjwtmiddleware.WithTokenExtractor(jwtmiddleware.MultiTokenExtractor(\n\t\tjwtmiddleware.AuthHeaderTokenExtractor,\n\t\tjwtmiddleware.CookieTokenExtractor(\"jwt\"),\n\t)),\n)\n```\n\n### URL Exclusions\n\nSkip JWT validation for specific URLs:\n\n```go\nmiddleware, err := jwtmiddleware.New(\n\tjwtmiddleware.WithValidator(jwtValidator),\n\tjwtmiddleware.WithExclusionUrls([]string{\n\t\t\"/health\",\n\t\t\"/metrics\",\n\t\t\"/public\",\n\t}),\n)\n```\n\n### Structured Logging\n\nEnable logging with `log/slog` or compatible loggers:\n\n```go\nimport \"log/slog\"\n\nlogger := slog.New(slog.NewJSONHandler(os.Stdout, \u0026slog.HandlerOptions{\n\tLevel: slog.LevelDebug,\n}))\n\nmiddleware, err := jwtmiddleware.New(\n\tjwtmiddleware.WithValidator(jwtValidator),\n\tjwtmiddleware.WithLogger(logger),\n)\n```\n\n### Custom Error Handling\n\nImplement custom error responses:\n\n```go\nfunc customErrorHandler(w http.ResponseWriter, r *http.Request, err error) {\n\tlog.Printf(\"JWT error: %v\", err)\n\n\tif errors.Is(err, jwtmiddleware.ErrJWTMissing) {\n\t\thttp.Error(w, \"No token provided\", http.StatusUnauthorized)\n\t\treturn\n\t}\n\n\tvar validationErr *jwtmiddleware.ValidationError\n\tif errors.As(err, \u0026validationErr) {\n\t\tswitch validationErr.Code {\n\t\tcase jwtmiddleware.ErrorCodeTokenExpired:\n\t\t\thttp.Error(w, \"Token expired\", http.StatusUnauthorized)\n\t\tcase jwtmiddleware.ErrorCodeInvalidIssuer:\n\t\t\thttp.Error(w, \"Untrusted issuer\", http.StatusUnauthorized)\n\t\tcase jwtmiddleware.ErrorCodeInvalidAudience:\n\t\t\thttp.Error(w, \"Audience mismatch\", http.StatusUnauthorized)\n\t\tcase jwtmiddleware.ErrorCodeInvalidSignature:\n\t\t\thttp.Error(w, \"Invalid signature\", http.StatusUnauthorized)\n\t\tdefault:\n\t\t\thttp.Error(w, \"Invalid token\", http.StatusUnauthorized)\n\t\t}\n\t\treturn\n\t}\n\n\thttp.Error(w, \"Unauthorized\", http.StatusUnauthorized)\n}\n\nmiddleware, err := jwtmiddleware.New(\n\tjwtmiddleware.WithValidator(jwtValidator),\n\tjwtmiddleware.WithErrorHandler(customErrorHandler),\n)\n```\n\n### Error Responses\n\nThe default error handler returns specific HTTP status codes and structured JSON responses for each type of JWT validation failure:\n\n| Failure | HTTP Status | `error` | `error_code` |\n|---------|------------|---------|-------------|\n| Malformed token | 401 | `invalid_token` | `token_malformed` |\n| Invalid algorithm | 401 | `invalid_token` | `invalid_algorithm` |\n| Invalid signature | 401 | `invalid_token` | `invalid_signature` |\n| Expired token | 401 | `invalid_token` | `token_expired` |\n| Not yet valid (nbf/iat) | 401 | `invalid_token` | `token_not_yet_valid` |\n| Invalid issuer | 401 | `invalid_token` | `invalid_issuer` |\n| Invalid audience | 401 | `invalid_token` | `invalid_audience` |\n| Invalid claims | 401 | `invalid_token` | `invalid_claims` |\n| JWKS fetch failed | 401 | `invalid_token` | `jwks_fetch_failed` |\n| JWKS key not found | 401 | `invalid_token` | `jwks_key_not_found` |\n| Missing token | 401 | `invalid_token` | — |\n\nExample response for an expired token:\n```json\n{\n  \"error\": \"invalid_token\",\n  \"error_description\": \"The access token expired\",\n  \"error_code\": \"token_expired\"\n}\n```\n\nAll error responses include RFC 6750 compliant `WWW-Authenticate` headers.\n\n**Available error code constants** (on the `jwtmiddleware` package):\n\n`ErrorCodeTokenMalformed`, `ErrorCodeTokenExpired`, `ErrorCodeTokenNotYetValid`,\n`ErrorCodeInvalidSignature`, `ErrorCodeInvalidAlgorithm`, `ErrorCodeInvalidIssuer`,\n`ErrorCodeInvalidAudience`, `ErrorCodeInvalidClaims`, `ErrorCodeJWKSFetchFailed`,\n`ErrorCodeJWKSKeyNotFound`\n\n### Clock Skew Tolerance\n\nAllow for time drift between servers:\n\n```go\njwtValidator, err := validator.New(\n\tvalidator.WithKeyFunc(keyFunc),\n\tvalidator.WithAlgorithm(validator.RS256),\n\tvalidator.WithIssuer(\"https://issuer.example.com/\"),\n\tvalidator.WithAudience(\"my-api\"),\n\tvalidator.WithAllowedClockSkew(30*time.Second),\n)\n```\n\n### DPoP (Demonstrating Proof-of-Possession)\n\nv3 adds support for [DPoP (RFC 9449)](https://datatracker.ietf.org/doc/html/rfc9449), which provides proof-of-possession for access tokens. This prevents token theft and replay attacks.\n\n#### DPoP Modes\n\n| Mode | Description | Use Case |\n|------|-------------|----------|\n| **DPoPAllowed** (default) | Accepts both Bearer and DPoP tokens | Migration period, backward compatibility |\n| **DPoPRequired** | Only accepts DPoP tokens | Maximum security |\n| **DPoPDisabled** | Ignores DPoP proofs, rejects DPoP scheme | Legacy systems |\n\n#### Basic DPoP Setup\n\n```go\nmiddleware, err := jwtmiddleware.New(\n\tjwtmiddleware.WithValidator(jwtValidator),\n\tjwtmiddleware.WithDPoPMode(jwtmiddleware.DPoPAllowed), // Default\n)\n```\n\n#### Require DPoP for Maximum Security\n\n```go\nmiddleware, err := jwtmiddleware.New(\n\tjwtmiddleware.WithValidator(jwtValidator),\n\tjwtmiddleware.WithDPoPMode(jwtmiddleware.DPoPRequired),\n)\n```\n\n#### Behind a Proxy\n\nWhen running behind a reverse proxy, configure trusted proxy headers:\n\n```go\nmiddleware, err := jwtmiddleware.New(\n\tjwtmiddleware.WithValidator(jwtValidator),\n\tjwtmiddleware.WithDPoPMode(jwtmiddleware.DPoPRequired),\n\tjwtmiddleware.WithStandardProxy(),  // Trust X-Forwarded-* headers\n)\n```\n\nSee the [DPoP examples](./examples/http-dpop-example) for complete working code.\n\n### Multiple Issuers (Multi-Tenant Support)\n\nAccept JWTs from multiple issuers simultaneously - perfect for multi-tenant SaaS applications, domain migrations, or enterprise deployments.\n\n#### When to Use What\n\nChoose the right issuer validation approach for your use case:\n\n| Approach | When to Use | Example Use Case |\n|----------|-------------|------------------|\n| **`WithIssuer`** (single) | You have one Auth0 tenant or identity provider | Simple API with single Auth0 tenant |\n| **`WithIssuers`** (static list) | You have a fixed set of issuers known at startup | - Small number of tenants (\u003c 10)\u003cbr\u003e- Rarely changing issuer list\u003cbr\u003e- Domain migration (old + new) |\n| **`WithIssuersResolver`** (dynamic) | Issuers determined at request time from database/context | - Multi-tenant SaaS with 100s+ tenants\u003cbr\u003e- Tenant-specific issuer configuration\u003cbr\u003e- Dynamic tenant onboarding |\n\n**Performance Comparison:**\n\n- **Single Issuer**: ~1ms validation (fastest, no issuer lookup)\n- **Static Multiple**: ~1ms validation (in-memory list check, very fast)\n- **Dynamic Resolver**: ~1-5ms validation (with caching), ~10-20ms (cache miss with DB query)\n\n**💡 Recommendation:** Start with `WithIssuer` or `WithIssuers` if possible. Only use `WithIssuersResolver` if you need dynamic tenant-based resolution.\n\n#### Choosing the Right JWKS Provider\n\nUse the correct JWKS provider based on your issuer validation approach:\n\n| JWKS Provider | Use With | Why |\n|---------------|----------|-----|\n| **`CachingProvider`** | `WithIssuer` (single issuer) | Optimized for single issuer, simpler configuration |\n| **`MultiIssuerProvider`** | `WithIssuers` or `WithIssuersResolver` | Handles dynamic JWKS routing per issuer, lazy loading |\n\n**⚠️ Important:**\n- Using `CachingProvider` with multiple issuers won't work correctly - it only caches JWKS for one issuer\n- Using `MultiIssuerProvider` with a single issuer works but adds unnecessary overhead\n- Always pair your issuer validation method with the appropriate provider\n\n**Example Mismatch (❌ Don't do this):**\n```go\n// WRONG: CachingProvider can't handle multiple issuers\nprovider := jwks.NewCachingProvider(jwks.WithIssuerURL(url))\nvalidator.New(\n    validator.WithIssuers([]string{\"issuer1\", \"issuer2\"}), // Won't work!\n    validator.WithKeyFunc(provider.KeyFunc),\n)\n```\n\n**Correct Usage (✅ Do this):**\n```go\n// RIGHT: MultiIssuerProvider for multiple issuers\nprovider := jwks.NewMultiIssuerProvider()\nvalidator.New(\n    validator.WithIssuers([]string{\"issuer1\", \"issuer2\"}),\n    validator.WithKeyFunc(provider.KeyFunc),\n)\n```\n\n#### Static Multiple Issuers\n\nConfigure a fixed list of allowed issuers:\n\n```go\n// Use MultiIssuerProvider for automatic JWKS routing\nprovider, err := jwks.NewMultiIssuerProvider(\n\tjwks.WithMultiIssuerCacheTTL(5*time.Minute),\n)\n\njwtValidator, err := validator.New(\n\tvalidator.WithKeyFunc(provider.KeyFunc),\n\tvalidator.WithAlgorithm(validator.RS256),\n\tvalidator.WithIssuers([]string{  // Multiple issuers!\n\t\t\"https://tenant1.auth0.com/\",\n\t\t\"https://tenant2.auth0.com/\",\n\t\t\"https://tenant3.auth0.com/\",\n\t}),\n\tvalidator.WithAudience(\"your-api-identifier\"),\n)\n```\n\n**Available Options:**\n\n| Option | Description | Default |\n|--------|-------------|---------|\n| `WithMultiIssuerCacheTTL` | JWKS cache refresh interval | 15 minutes |\n| `WithMultiIssuerHTTPClient` | Custom HTTP client for JWKS fetching | 30s timeout |\n| `WithMultiIssuerCache` | Custom cache implementation (e.g., Redis) | In-memory |\n| `WithMaxProviders` | Maximum issuer providers to cache | 100 |\n\n#### Large-Scale Multi-Tenant (100+ Tenants)\n\nFor applications with many tenants, use Redis and LRU eviction to manage memory:\n\n```go\n// Create Redis cache (see examples/http-multi-issuer-redis-example)\nredisCache := \u0026RedisCache{\n\tclient: redis.NewClient(\u0026redis.Options{Addr: \"localhost:6379\"}),\n\tttl:    5 * time.Minute,\n}\n\n// Configure provider with Redis and LRU eviction\nprovider, err := jwks.NewMultiIssuerProvider(\n\tjwks.WithMultiIssuerCacheTTL(5*time.Minute),\n\tjwks.WithMultiIssuerCache(redisCache),  // Share JWKS across instances\n\tjwks.WithMaxProviders(1000),            // Keep max 1000 providers in memory\n)\n\njwtValidator, err := validator.New(\n\tvalidator.WithKeyFunc(provider.KeyFunc),\n\tvalidator.WithAlgorithm(validator.RS256),\n\tvalidator.WithIssuers(allowedIssuers),  // Your tenant list\n\tvalidator.WithAudience(\"your-api-identifier\"),\n)\n```\n\n**Why Redis for 100+ Tenants?**\n- 📦 **Shared Cache**: JWKS data shared across multiple application instances\n- 💾 **Memory Efficiency**: Offload JWKS storage from application memory\n- 🔄 **Automatic Expiry**: Redis handles TTL and eviction\n- 📈 **Scalability**: Handles thousands of tenants without memory bloat\n\n#### Dynamic Issuer Resolution\n\nDetermine allowed issuers at request time based on context (tenant ID, database, etc.):\n\n```go\n// For many tenants, use Redis and limit cached providers\nprovider, err := jwks.NewMultiIssuerProvider(\n\tjwks.WithMultiIssuerCache(redisCache),  // Optional: Redis for JWKS caching\n\tjwks.WithMaxProviders(500),             // Optional: LRU limit for memory control\n)\n\njwtValidator, err := validator.New(\n\tvalidator.WithKeyFunc(provider.KeyFunc),\n\tvalidator.WithAlgorithm(validator.RS256),\n\tvalidator.WithIssuersResolver(func(ctx context.Context) ([]string, error) {\n\t\t// Extract tenant from context (set by your middleware)\n\t\ttenantID, _ := ctx.Value(\"tenant\").(string)\n\n\t\t// Check cache (user-managed caching for optimal performance)\n\t\tif cached, found := cache.Get(tenantID); found {\n\t\t\treturn cached, nil\n\t\t}\n\n\t\t// Query database for tenant's allowed issuers\n\t\tissuers, err := database.GetIssuersForTenant(ctx, tenantID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// Cache for 5 minutes\n\t\tcache.Set(tenantID, issuers, 5*time.Minute)\n\t\treturn issuers, nil\n\t}),\n\tvalidator.WithAudience(\"your-api-identifier\"),\n)\n```\n\n**Key Features:**\n- 🔒 **Security**: Issuer validated BEFORE fetching JWKS (prevents SSRF attacks)\n- ⚡ **Performance**: Per-issuer JWKS caching with lazy loading\n- 🎯 **Flexibility**: User-controlled caching strategy (in-memory, Redis, etc.)\n- 🔄 **Thread-Safe**: Concurrent request handling with double-checked locking\n\n**Use Cases:**\n- Multi-tenant SaaS applications\n- Domain migration (support old and new domains simultaneously)\n- Enterprise deployments with multiple Auth0 tenants\n- Connected accounts from different identity providers\n\nSee the [multi-issuer examples](./examples/http-multi-issuer-example) for complete working code.\n\n#### Security Requirements for Multiple Custom Domains (MCD)\n\nWhen using `WithIssuers` or `WithIssuersResolver` to support Multiple Custom Domains (MCD), you are responsible for ensuring that only trusted issuer domains are returned.\n\nMisconfiguring the issuer list or resolver is a critical security risk. It can cause the middleware to:\n- Accept access tokens from unintended issuers\n- Make discovery or JWKS requests to unintended domains\n\n\u003e **Note for Auth0 users:** The `WithIssuers` / `WithIssuersResolver` configuration for MCD is intended only for multiple custom domains that belong to the same Auth0 tenant. It is not a supported mechanism for connecting multiple Auth0 tenants to a single API. Each domain in your issuer list should resolve to the same underlying Auth0 tenant.\n\n**Dynamic Resolver Warning:**\nIf your `WithIssuersResolver` function uses request-derived values (such as headers, query parameters, or path segments), do not trust those values directly. Use them only to map known and expected request values to a fixed allowlist of issuer domains that you control.\n\nIn particular:\n- Request headers like `Host`, `X-Forwarded-Host`, or `Origin` may be influenced by clients, proxies, or load balancers depending on your deployment setup\n- The unverified `iss` claim from the token has not been signature-verified yet and must not be trusted by itself\n\nIf your deployment relies on reverse proxies or load balancers, ensure that host-related request information is treated as trusted only when it comes from trusted infrastructure. Misconfigured proxy handling can cause the middleware to trust unintended issuer domains.\n\n**Example of a safe resolver pattern:**\n\nThe resolver receives `context.Context` with the unverified `iss` claim available via `validator.IssuerFromContext`. Use this only to check membership in a fixed allowlist, never as a trusted value for logging, database queries, or external calls.\n\n```go\n// allowedIssuers is a fixed set of trusted issuer URLs.\nvar allowedIssuers = map[string]bool{\n    \"https://brand-a.auth.example.com/\":    true,\n    \"https://brand-a-jp.auth.example.com/\": true,\n    \"https://brand-b.auth.example.com/\":    true,\n}\n\nvalidator.WithIssuersResolver(func(ctx context.Context) ([]string, error) {\n    // Return the full allowlist. The middleware will check the token's\n    // iss claim against this list before making any JWKS requests.\n    issuers := make([]string, 0, len(allowedIssuers))\n    for iss := range allowedIssuers {\n        issuers = append(issuers, iss)\n    }\n    return issuers, nil\n})\n```\n\nThe resolver's `context.Context` does not automatically include HTTP request information. If your resolver needs request-derived values (such as the host or tenant ID), an upstream middleware must explicitly set them in the context before the JWT middleware runs. Without that, the context will only contain the unverified `iss` claim via `validator.IssuerFromContext`.\n\nFor per-request filtering (e.g., tenant-scoped resolution), use trusted context values set by upstream middleware:\n\n```go\nvalidator.WithIssuersResolver(func(ctx context.Context) ([]string, error) {\n    // tenantID must be set by a trusted upstream middleware,\n    // not derived from the token or untrusted request headers.\n    tenantID, ok := ctx.Value(tenantContextKey).(string)\n    if !ok {\n        return nil, fmt.Errorf(\"missing tenant in context\")\n    }\n\n    switch tenantID {\n    case \"brand-a\":\n        return []string{\n            \"https://brand-a.auth.example.com/\",\n            \"https://brand-a-jp.auth.example.com/\",\n        }, nil\n    case \"brand-b\":\n        return []string{\n            \"https://brand-b.auth.example.com/\",\n        }, nil\n    default:\n        return nil, fmt.Errorf(\"unknown tenant: %s\", tenantID)\n    }\n})\n```\n\n## Examples\n\nFor complete working examples, check the [examples](./examples) directory:\n\n- **[http-example](./examples/http-example)** - Basic HTTP server with HMAC\n- **[http-jwks-example](./examples/http-jwks-example)** - Production setup with JWKS and Auth0\n- **[http-multi-issuer-example](./examples/http-multi-issuer-example)** - Multiple issuers with static list\n- **[http-multi-issuer-redis-example](./examples/http-multi-issuer-redis-example)** - Multi-tenant with Redis cache and LRU eviction\n- **[http-dynamic-issuer-example](./examples/http-dynamic-issuer-example)** - Dynamic issuer resolution with caching\n- **[http-dpop-example](./examples/http-dpop-example)** - DPoP support (allowed mode)\n- **[http-dpop-required](./examples/http-dpop-required)** - DPoP required mode\n- **[http-dpop-disabled](./examples/http-dpop-disabled)** - DPoP disabled mode\n- **[http-dpop-trusted-proxy](./examples/http-dpop-trusted-proxy)** - DPoP behind reverse proxy\n- **[gin-example](./examples/gin-example)** - Integration with Gin framework\n- **[echo-example](./examples/echo-example)** - Integration with Echo framework\n- **[iris-example](./examples/iris-example)** - Integration with Iris framework\n\n## Supported Algorithms\n\nv3 supports 14 signature algorithms:\n\n| Type | Algorithms |\n|------|-----------|\n| HMAC | HS256, HS384, HS512 |\n| RSA | RS256, RS384, RS512 |\n| RSA-PSS | PS256, PS384, PS512 |\n| ECDSA | ES256, ES384, ES512, ES256K |\n| EdDSA | EdDSA (Ed25519) |\n\n### Symmetric vs Asymmetric: When to Use What\n\nChoose the right algorithm type based on your use case:\n\n| Algorithm Type | Key Distribution | When to Use | Example Use Case |\n|----------------|------------------|-------------|------------------|\n| **Symmetric (HMAC)** | Shared secret between issuer and API | - Simple single-service architecture\u003cbr\u003e- You control both token creation and validation\u003cbr\u003e- Internal microservices communication | Backend API validating its own tokens |\n| **Asymmetric (RS256, ES256, EdDSA)** | Public/private key pair (issuer has private, API has public) | - **Production with Auth0 or external OAuth providers**\u003cbr\u003e- Multiple services validating tokens\u003cbr\u003e- You don't control token creation\u003cbr\u003e- Security-critical applications | Production API with Auth0, Okta, or any OAuth provider |\n\n**🔐 Security Recommendations:**\n\n1. **For Production with Auth0/OAuth providers: Use RS256** (default)\n   - Industry standard for OAuth 2.0 and OpenID Connect\n   - Auth0 uses RS256 by default\n   - Public key rotation supported via JWKS\n   - No shared secrets to manage\n\n2. **For Modern Applications: Consider EdDSA**\n   - Fastest signing and verification\n   - Smaller signatures (better bandwidth)\n   - Immune to timing attacks\n   - Supported by Auth0 (must enable in dashboard)\n\n3. **For Internal Services Only: HMAC is acceptable**\n   - Simplest to configure (just a shared secret)\n   - Fast performance\n   - ⚠️ But: Secret must be protected and distributed securely\n\n4. **Avoid using HMAC with external identity providers**\n   - Can't use with Auth0/Okta (they use asymmetric keys)\n   - Shared secret is a security risk at scale\n\n**Example Configurations:**\n\nProduction with Auth0 (RS256):\n```go\nprovider, _ := jwks.NewCachingProvider(jwks.WithIssuerURL(issuerURL))\nvalidator.New(\n    validator.WithKeyFunc(provider.KeyFunc),\n    validator.WithAlgorithm(validator.RS256), // Standard for OAuth\n    validator.WithIssuer(issuerURL.String()),\n    validator.WithAudience(\"your-api\"),\n)\n```\n\nInternal microservices (HMAC):\n```go\nvalidator.New(\n    validator.WithKeyFunc(func(ctx context.Context) (any, error) {\n        return []byte(os.Getenv(\"JWT_SECRET\")), nil\n    }),\n    validator.WithAlgorithm(validator.HS256), // Simple shared secret\n    validator.WithIssuer(\"internal-service\"),\n    validator.WithAudience(\"my-api\"),\n)\n```\n\nHigh-security applications (EdDSA):\n```go\nprovider, _ := jwks.NewCachingProvider(jwks.WithIssuerURL(issuerURL))\nvalidator.New(\n    validator.WithKeyFunc(provider.KeyFunc),\n    validator.WithAlgorithm(validator.EdDSA), // Modern, fast, secure\n    validator.WithIssuer(issuerURL.String()),\n    validator.WithAudience(\"your-api\"),\n)\n```\n\n## Migration from v2\n\nSee [MIGRATION_GUIDE.md](./MIGRATION_GUIDE.md) for a complete guide on upgrading from v2 to v3.\n\nKey changes:\n- Pure options pattern for all components\n- Type-safe claims with generics\n- New JWT library (lestrrat-go/jwx v3)\n- Core-Adapter architecture\n\n## Feedback\n\n### Contributing\n\nWe appreciate feedback and contribution to this repo! Before you get started, please see the following:\n\n- [Contribution Guide](./CONTRIBUTING.md)\n- [Auth0's General Contribution Guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)\n- [Auth0's Code of Conduct Guidelines](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)\n\n### Raise an issue\n\nTo provide feedback or report a bug, [please raise an issue on our issue tracker](https://github.com/auth0/go-jwt-middleware/issues).\n\n### Vulnerability Reporting\n\nPlease do not report security vulnerabilities on the public Github issue tracker. The [Responsible Disclosure Program](https://auth0.com/responsible-disclosure-policy) details the procedure for disclosing security issues.\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://cdn.auth0.com/website/sdks/logos/auth0_light_mode.png\" width=\"150\"\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://cdn.auth0.com/website/sdks/logos/auth0_dark_mode.png\" width=\"150\"\u003e\n    \u003cimg alt=\"Auth0 Logo\" src=\"https://cdn.auth0.com/website/sdks/logos/auth0_light_mode.png\" width=\"150\"\u003e\n  \u003c/picture\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003eAuth0 is an easy to implement, adaptable authentication and authorization platform.\u003cbr /\u003eTo learn more checkout \u003ca href=\"https://auth0.com/why-auth0\"\u003eWhy Auth0?\u003c/a\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003eThis project is licensed under the MIT license. See the \u003ca href=\"./LICENSE.md\"\u003e LICENSE\u003c/a\u003e file for more info.\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fauth0%2Fgo-jwt-middleware","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fauth0%2Fgo-jwt-middleware","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fauth0%2Fgo-jwt-middleware/lists"}