Go module implementing various JWx (JWA/JWE/JWK/JWS/JWT, otherwise known as JOSE) technologies.
If you are using this module in your product or your company, please add your product and/or company name in the Wiki! It really helps keeping up our motivation.
GOEXPERIMENT=jsonv2go get github.com/lestrrat-go/jwx/v4
If you use Claude Code, install the bundled jwx-dev-v4 skill so the assistant can guide you through using this library — picking algorithms, parsing/signing JWTs, working with JWS/JWE/JWK, and avoiding common footguns:
/plugin marketplace add lestrrat-go/jwx
/plugin install jwx-dev-v4
The skill is scoped to v4 only. It is intended for developers using jwx, not for working on the library itself.
If you are migrating from github.com/lestrrat-go/jwx/v3, see MIGRATION.md for a step-by-step guide with before/after code examples. For a complete list of breaking changes and new features, see Changes-v4.md.
| Feature | Description |
|---|---|
| Complete JWA/JWE/JWK/JWS/JWT coverage | Not just JWT + minimum tool set. Supports JWS messages with multiple signatures (compact and JSON serialization), JWS with detached payload, JWS with unencoded payload (RFC 7797), JWE messages with multiple recipients (compact and JSON serialization). Most operations work with either JWK or raw keys (e.g. *rsa.PrivateKey, *ecdsa.PrivateKey). |
| Opinionated, uniform API | Everything is symmetric and follows a standard convention: jws.Parse/Verify/Sign, jwe.Parse/Encrypt/Decrypt. Arguments are organized as explicit required parameters and optional WithXXXX() style options. |
| Post-quantum cryptography | Supports ML-KEM, ML-DSA, and HPKE. |
| Extension module architecture | Opt-in features via extension modules. See Extension Modules. |
| JWK Caching | jwkcache extension to always keep a JWKS up-to-date. |
| Bazel Support | Bazel-ready. |
package examples_test
import (
"bytes"
"fmt"
"net/http"
"time"
"github.com/lestrrat-go/jwx/v4/jwa"
"github.com/lestrrat-go/jwx/v4/jwe"
"github.com/lestrrat-go/jwx/v4/jwk"
"github.com/lestrrat-go/jwx/v4/jws"
"github.com/lestrrat-go/jwx/v4/jwt"
)
func Example() {
// Parse, serialize, slice and dice JWKs!
privkey, err := jwk.ParseKey(jsonRSAPrivateKey)
if err != nil {
fmt.Printf("failed to parse JWK: %s\n", err)
return
}
pubkey, err := jwk.PublicKeyOf(privkey)
if err != nil {
fmt.Printf("failed to get public key: %s\n", err)
return
}
// Work with JWTs!
{
// Build a JWT!
tok, err := jwt.NewBuilder().
Issuer(`github.com/lestrrat-go/jwx`).
IssuedAt(time.Now()).
Build()
if err != nil {
fmt.Printf("failed to build token: %s\n", err)
return
}
// Sign a JWT!
signed, err := jwt.Sign(tok, jwt.WithKey(jwa.RS256(), privkey))
if err != nil {
fmt.Printf("failed to sign token: %s\n", err)
return
}
// Verify a JWT!
{
verifiedToken, err := jwt.Parse(signed, jwt.WithKey(jwa.RS256(), pubkey))
if err != nil {
fmt.Printf("failed to verify JWS: %s\n", err)
return
}
_ = verifiedToken
}
// Work with *http.Request!
{
req, _ := http.NewRequest(http.MethodGet, `https://github.com/lestrrat-go/jwx`, nil)
req.Header.Set(`Authorization`, fmt.Sprintf(`Bearer %s`, signed))
verifiedToken, err := jwt.ParseRequest(req, jwt.WithKey(jwa.RS256(), pubkey))
if err != nil {
fmt.Printf("failed to verify token from HTTP request: %s\n", err)
return
}
_ = verifiedToken
}
}
// Encrypt and Decrypt arbitrary payload with JWE!
{
encrypted, err := jwe.Encrypt(payloadLoremIpsum, jwe.WithKey(jwa.RSA_OAEP_256(), jwkRSAPublicKey))
if err != nil {
fmt.Printf("failed to encrypt payload: %s\n", err)
return
}
decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP_256(), jwkRSAPrivateKey))
if err != nil {
fmt.Printf("failed to decrypt payload: %s\n", err)
return
}
if !bytes.Equal(decrypted, payloadLoremIpsum) {
fmt.Printf("verified payload did not match\n")
return
}
}
// Sign and Verify arbitrary payload with JWS!
{
signed, err := jws.Sign(payloadLoremIpsum, jws.WithKey(jwa.RS256(), jwkRSAPrivateKey))
if err != nil {
fmt.Printf("failed to sign payload: %s\n", err)
return
}
verified, err := jws.Verify(signed, jws.WithKey(jwa.RS256(), jwkRSAPublicKey))
if err != nil {
fmt.Printf("failed to verify payload: %s\n", err)
return
}
if !bytes.Equal(verified, payloadLoremIpsum) {
fmt.Printf("verified payload did not match\n")
return
}
}
// OUTPUT:
}
source: examples/jwx_readme_example_test.go
This module implements the following specifications:
| Package | Specification |
|---|---|
| jwa | RFC 7518 (JSON Web Algorithms) |
| jwk | RFC 7517 (JSON Web Key), RFC 7638 (JWK Thumbprint), RFC 8037 (CFRG Curves) |
| jws | RFC 7515 (JSON Web Signature), RFC 7797 (Unencoded Payload) |
| jwe | RFC 7516 (JSON Web Encryption), draft-ietf-jose-hpke-encrypt (HPKE) |
| jwt | RFC 7519 (JSON Web Token) |
Additionally supported via the main module or extension modules:
| Specification | Support |
|---|---|
| FIPS 203 (ML-KEM) | JWE key encapsulation via github.com/jwx-go/mlkem: ML-KEM-768, ML-KEM-1024, hybrid variants (draft-ietf-jose-pqc-kem) |
| FIPS 204 (ML-DSA) | JWS signatures via github.com/jwx-go/mldsa |
My goal was to write a server that heavily uses JWK and JWT. At first glance the libraries that already exist seemed sufficient, but soon I realized that
For example, a certain library looks like it had most of JWS, JWE, JWK covered, but then it lacked the ability to include private claims in its JWT responses. Another library had support of all the private claims, but completely lacked in its flexibility to generate various different response formats.
Because I was writing the server side (and the client side for testing), I needed the entire JOSE toolset to properly implement my server, and they needed to be flexible enough to fulfill the entire spec that I was writing.
So here's github.com/lestrrat-go/jwx/v4. This library is extensible, customizable, and hopefully well organized to the point that it is easy for you to slice and dice it.
There are several other major Go modules that handle JWT and related data formats, so why should you use this library?
From a purely functional perspective, the only major difference is this: Whereas most other projects only deal with what they seem necessary to handle JWTs, this module handles the entire spectrum of JWS, JWE, JWK, and JWT.
That is, if you need to not only parse JWTs, but also to control JWKs, or if you need to handle payloads that are NOT JWTs, you should probably consider using this module. You should also note that JWT is built on top of those other technologies. You simply cannot have a complete JWT package without implementing the entirety of JWS/JWE/JWK, which this library does.
Next, from an implementation perspective, this module differs significantly from others in that it tries very hard to expose only the APIs, and not the internal data. For example, individual JWT claims are not accessible through struct field lookups. You need to use one of the getter methods.
This is because this library takes the stance that the end user is fully capable and even willing to shoot themselves on the foot when presented with a lax API. By making sure that users do not have access to open structs, we can protect users from doing silly things like creating incomplete structs, or access the structs concurrently without any protection. This structure also allows us to put extra smarts in the structs, such as doing the right thing when you want to parse / write custom fields (this module does not require the user to specify alternate structs to parse objects with custom fields)
In the end I think it comes down to your usage pattern, and priorities. Some general guidelines that come to mind are:
Otherwise, feel free to choose something else.
For bug reports and feature requests, please try to follow the issue templates as much as possible. For either bug reports or feature requests, failing tests are even better.
Please make sure to include tests that exercise the changes you made.
If you are editing auto-generated files (those files with the _gen.go suffix, please make sure that you do the following:
internal/jwxcodegen/cmd/jwxcodegen/)make generate (or go generate) to generate the new codePlease try discussions first.
If you use this software to build products in a for-profit organization, we ask you to consider contributing back to FOSS in the following manner:
This is NOT a licensing term: you are still free to use this software according to the license it comes with. This clause is only a plea for people to acknowledge the work from FOSS developers whose work you rely on each and everyday.