Library / SDK
auth0/go-jwt-middleware avatar
auth0/go-jwt-middleware

auth0/go-jwt-middleware: JWT validation as a Go HTTP middleware

A Middleware for Go Programming Language to check for JWTs on HTTP requests

1,208 stars212 forksGoMIT

At a glance

What is it?
An Auth0-maintained Go library that validates JSON Web Tokens on incoming HTTP requests through a functional options API, with gRPC interceptors, generic claim types, DPoP support and a JWKS provider. The trade-off is that the v3 line requires Go 1.25 in practice even though the README says Go 1.24+.
Who is it for?
go-jwt-middleware fits teams serving Go HTTP APIs that validate Auth0-issued tokens and want one validator shared by several transports, plus RFC 6750 error responses and optional DPoP without writing that layer themselves.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the library does for a Go service

go-jwt-middleware is a middleware for the Go programming language that checks for JWTs on HTTP requests. The user it serves is a backend team that has an API protected by an identity provider and wants a request to be rejected before it reaches a handler unless it carries a valid token.

The v3 line is a substantial rewrite rather than an increment. The README describes the changes under a series of headings, and the biggest structural one is a core-adapter architecture with framework-agnostic validation logic that can be reused across HTTP, gRPC and other transports. The diagram in the README is short:

code
HTTP Middleware → Core Engine → Validator
gRPC Interceptor → Core Engine → Validator

That layout is visible in the repository tree as well. The top level contains `core/`, `validator/`, `integrations/`, `jwks/` and `internal/` directories next to `middleware.go`, `extractor.go`, `error_handler.go`, `proxy.go`, `dpop.go` and `option.go`, so the separation is a directory layout and not just a description.

The library is MIT licensed with a `LICENSE` file at the root, is not archived, and the last push was on 2026-10-14, the same day as the v3.4.0 release. The recent release history, v3.4.0, v3.3.0 on 2026-07-31 and v3.2.0 on 2026-05-15, shows a release cadence of roughly every two to three months.

Everything is a functional option now

The most visible v3 change is the pure options pattern, described as giving better IDE support and compile-time validation. The validator is configured like this:

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

The four options are the whole required surface: where the key comes from, which algorithm is acceptable, which issuer must have signed it, and which audience must match. The algorithm is pinned to a named constant, `validator.RS256` or `validator.HS256`, rather than left as a string, so a typo is a compile error.

The middleware is configured the same way:

go
jwtmiddleware.New(
    jwtmiddleware.WithValidator(jwtValidator),
)

The `option.go` file at the repository root is the implementation of that pattern, and `option_test.go` sits next to it.

The trade-off of the options pattern is that it moves configuration out of a struct literal and into a list of calls, which reads well but means every knob is a separate exported symbol. For a library with a large surface, that is a lot of package-level names, and the practical consequence is that the godoc page becomes the place where you discover what you can configure.

gRPC interceptors share the same validator

The gRPC integration is the part of v3 that goes beyond HTTP. The README describes native gRPC integration with interceptors for unary and streaming methods:

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

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

grpcServer := grpc.NewServer(
    grpc.UnaryInterceptor(interceptor.UnaryServerInterceptor()),
    grpc.StreamInterceptor(interceptor.StreamServerInterceptor()),
)

The important detail is that the validator passed to the interceptor is the same object the HTTP middleware would use, which is the payoff of the core-adapter design. `WithExcludedMethods("/health.Check")` shows the escape hatch for unauthenticated endpoints, expressed as a gRPC full method name.

The gRPC path also supports Multiple Custom Domains through `WithIssuers` or `WithIssuersResolver`, and automatic JWKS routing through `MultiIssuerProvider`. The README points at `integrations/grpc/doc.go` for the details, and that file exists in the tree.

The examples directory is where the breadth of the library becomes visible: it holds separate example applications for echo, gin, iris, gRPC, plain HTTP, JWKS, dynamic issuer, multi-issuer, multi-issuer with redis, and four DPoP variants, one of them disabled and one a trusted proxy. Thirteen example applications is a lot of surface, and it is also the best available guide to the configuration matrix.

Type-safe claims through Go generics

Claim retrieval is typed rather than a map lookup:

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

The type parameter is `*validator.ValidatedClaims`, and the README says this uses Go 1.24+ generics for compile-time type safety. The function reads from the request context, so it is only meaningful on a request that has already passed through `middleware.CheckJWT`.

This is a real change in failure mode. With a context-based map of claims, a missing claim is a nil dereference or a silent zero value at runtime. With a generic wrapper, the type is checked when the code compiles, and the error return still handles the case where the middleware did not run.

The `validator.ValidatedClaims` type is the library's own claims struct, and it is what the validation layer produces. If you need your application's own claims type, the README's example does not show one, so the practical route is to use `ValidatedClaims` and read the fields you need from it.

The cost of the generic approach is a Go version floor, and that is where the documentation and the module disagree.

go.mod asks for Go 1.25 while the README says Go 1.24

The requirements section states Go 1.24+ and says the library follows the same support policy as Go, where the last two major Go releases are actively supported and compatibility issues will be fixed. That policy statement is about which Go versions get fixes, not about the minimum version to build.

The module file is more specific. `go.mod` declares:

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

go 1.25.0

So the build floor is Go 1.25.0 in the module itself, one minor version above what the README advertises. A team whose CI image is on Go 1.24 will hit a toolchain error on `go get` or on build, and the README will not have told them. The support policy line also means that when Go 1.27 or a later release ships, the floor and the stated policy will need to be read together to know which versions actually get patches.

The rest of the dependency list is short and readable. The direct requirements are `github.com/lestrrat-go/jwx/v3` at v3.0.13 for the JWT and JWKS handling, `google.golang.org/grpc` at v1.84.0, `github.com/google/go-cmp` and `github.com/stretchr/testify` for tests. The README credits the v3 upgrade of jwx as giving better performance and security, support for 14 signature algorithms including EdDSA and ES256K, and improved JWKS handling with automatic `kid` matching. The indirect list shows the usual jwx and protobuf dependency tree.

DPoP, RFC 6750 errors and the secure defaults

The security section of the README lists RFC 6750 compliant error responses, secure defaults described as credentials required and clock skew equal to zero, and DPoP support under RFC 9449 for proof-of-possession tokens.

DPoP is enabled by a mode option:

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

The mode name is the whole contract: `DPoPRequired` means a token without a DPoP proof is not acceptable. That is a change to what your API accepts, not a cosmetic toggle, and it is why the examples directory includes separate `http-dpop-disabled`, `http-dpop-example` and `http-dpop-required` applications, plus a `http-dpop-trusted-proxy` variant for the case where the request arrives through a proxy that forwards the proof.

Clock skew of zero is another default worth understanding. It means a token whose `exp` is judged expired by even a second fails, with no leeway for clock differences between your servers and the issuer. Whether that is right depends on your clock discipline.

Logging is also part of the middleware construction rather than a separate concern, using the standard library structured logger:

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

The `proxy.go` and `error_handler.go` files at the root are the implementation of the trusted proxy and the error response behaviour, and both have matching test files.

Building, testing and the example suite

The Makefile is a straightforward Go project workflow. Dependencies are vendored with `go mod vendor` through the `deps` target, and golangci-lint v2.6.2 is installed by the `$(GO_BIN)/golangci-lint` target, and `govulncheck` is installed from `golang.org/x/vuln/cmd/govulncheck@latest`.

Tests run with coverage in atomic mode, and a single test can be selected by passing a filter:

shell
make test FILTER="Test_invalidError"

The example applications have their own integration test target, and the target's help text documents two environment variables, `SKIP_EXAMPLES` and `ONLY_EXAMPLES`, both taking a pattern, so you can run a single example's integration test or exclude a flaky one. That is a detail worth knowing, since an example suite that is slow or environment-sensitive is a common reason CI for a library like this gets disabled.

The repository also carries `.golangci.yml` and `.semgrepignore` at the root, an `opslevel.yml`, a `CHANGELOG.md`, a `CONTRIBUTING.md` and a `MIGRATION_GUIDE.md` for moving from v2 to v3. The migration guide is the file to read first if you are upgrading an existing service, because the options pattern means the v2 struct-based configuration has no direct equivalent and every call site has to change.

Editorial conclusion

go-jwt-middleware fits teams serving Go HTTP APIs that validate Auth0-issued tokens and want one validator shared by several transports, plus RFC 6750 error responses and optional DPoP without writing that layer themselves. It does not fit a service that issues and verifies its own tokens with a home-grown scheme, since the validator is built around issuer and audience checks, and it does not fit a codebase pinned below Go 1.25, because `go.mod` declares `go 1.25.0` even though the README lists Go 1.24+. Before adopting it, check that `go.mod` in your service can move to the version the module requires, read `MIGRATION_GUIDE.md` if you are coming from v2, and decide whether `WithDPoPMode(jwtmiddleware.DPoPRequired)` should be your default, since that setting changes which tokens your API accepts.

Frequently asked questions

How do I add JWT validation to a Go HTTP service with go-jwt-middleware?

Create a validator with `validator.New` and the `WithKeyFunc`, `WithAlgorithm`, `WithIssuer` and `WithAudience` options, then wrap your handler with `middleware.CheckJWT(handler)`. The README's basic example uses `validator.HS256` with a shared secret.

What Go version does auth0/go-jwt-middleware require?

The README lists Go 1.24+, but `go.mod` declares `go 1.25.0`, so the module itself requires Go 1.25.0 or newer. The project says it follows the same support policy as Go, where the last two major releases are supported.

How do I enable DPoP token protection?

Pass `jwtmiddleware.WithDPoPMode(jwtmiddleware.DPoPRequired)` when creating the middleware. DPoP is the RFC 9449 proof-of-possession mechanism, and requiring it changes which tokens your API accepts.

Can go-jwt-middleware be used with gRPC?

Yes. The v3 line ships a gRPC integration with unary and streaming server interceptors, built from `jwtgrpc.New` with `WithValidator` and `WithExcludedMethods`. The same validator instance can be shared with the HTTP middleware.

How do I retrieve the claims of the current request?

Use `jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())`. The README describes this as type-safe claims retrieval using Go 1.24+ generics, and it returns an error if the claims are not present.

Official sources

  1. auth0/go-jwt-middleware on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/auth0-go-jwt-middleware.svg)](https://hysenlabs.com/projects/auth0-go-jwt-middleware)

Was this useful?