Skip to main content

jwx-guide-v4

Guide for developing Go applications with github.com/lestrrat-go/jwx v4 — parse/sign JWTs, work with JWS/JWE/JWK, pick algorithms, and avoid the common footguns. For developers using jwx, not for developing the library itself.

Zur Installation springen

Quellinformationen

Repository
lestrrat-go/jwx
Letzte Quellaktivität
3. September 2026 um 09:54
Erkannte Sprache von SKILL.md
Englisch
Sterne
2.426
Forks
195

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
jwx-guide-v4
description
Guide for developing Go applications with github.com/lestrrat-go/jwx v4 — parse/sign JWTs, work with JWS/JWE/JWK, pick algorithms, and avoid the common footguns. For developers using jwx, not for developing the library itself.
# jwx-guide-v4 This skill helps you assist Go developers who are **using** `github.com/lestrrat-go/jwx/v4` in their own projects. It is scoped to **v4** only. There is no equivalent skill for v3 or v2; for those, work from the version's own `docs/` directory and pkg.go.dev. Do not apply v4 rules to a v3 or v2 codebase — the `jwa` identifiers are constants there, not functions, and the `jwk` and error APIs differ. ## Where to look things up You will not have this repository checked out. To verify an API claim before answering, use these sources, in order of preference: 1. **Go module cache** — if the user's project depends on jwx, the source is on disk: ```bash echo "$(go env GOMODCACHE)/github.com/lestrrat-go/jwx/v4@$(go list -m -f '{{.Version}}' github.com/lestrrat-go/jwx/v4)" ``` This directory contains the full source tree including `docs/`, `jwt/`, `jws/`, `jwe/`, `jwk/`, `jwa/`. 2. **pkg.go.dev** — canonical API reference for any exported symbol: - `https://pkg.go.dev/github.com/lestrrat-go/jwx/v4` - `https://pkg.go.dev/github.com/lestrrat-go/jwx/v4/jwt` (etc. per subpackage) 3. **GitHub** — narrative documentation lives at the repo: - `https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/01-jwt.md` - `https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/02-jws.md` - `https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/03-jwe.md` - `https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/04-jwk.md` - `https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/10-extensions.md` - `https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/99-faq.md` 4. **Examples repo** — runnable usage patterns. The repo's `README.md` is a topical index that maps "what do I want to do" → "which `*_example_test.go` file." Fetch the README first when looking for an example by topic; then fetch the linked file: - `https://github.com/jwx-go/examples/blob/develop/v4/README.md` When the user's question goes beyond the patterns in this skill (custom claim types, JWE recipients with per-recipient headers, nested JWS+JWE serializers, custom key providers, base64 backend swap, performance tuning), fetch from these sources rather than guessing. ## Prerequisites - **Go 1.26+** is required. v4 uses generics features and stdlib additions (`errors.AsType[T]`) that are new in 1.26. - **`GOEXPERIMENT=jsonv2`** must be set on Go 1.26 for every `go build`/`go test`/`go run`, because v4 depends on `encoding/json/v2`. Without it → builds fail with `build constraints exclude all Go files`. NEVER set it on Go 1.27+ → `encoding/json/v2` is in the standard library there, and naming the experiment rebuilds the standard library under a non-default configuration. Sub-package map: | Package | Role | |---------|------| | `jwa` | Algorithm identifiers as **functions**: `jwa.RS256()`, `jwa.ES256()`, `jwa.HS256()`, `jwa.A256GCM()`, `jwa.RSA_OAEP_256()`, `jwa.EdDSAEd25519()`, etc. | | `jwk` | JSON Web Keys: parsing, generating, import/export between `jwk.Key` and `crypto.*` keys, key sets. | | `jws` | Sign and verify arbitrary payloads (compact or JSON serialization). | | `jwe` | Encrypt and decrypt arbitrary payloads. | | `jwt` | JWT tokens — claims, signing, verification + validation. Wraps `jws`. | | `jwt/openid` | OpenID Connect ID-token-flavored claims. | ## Critical rules (read first) 1. **`jwt.Parse` verifies AND validates by default.** A bare `jwt.Parse(data)` errors because no key was supplied. To intentionally skip both, use `jwt.ParseInsecure`. To verify but skip claim validation, pass `jwt.WithValidate(false)`. This is deliberately asymmetric vs. `jws.Parse`/`jwe.Parse` (which only parse). Do not "correct" it. 2. **Always pin the algorithm on the verify side.** `jwt.WithKey(jwa.RS256(), key)`, `jws.WithKey(jwa.ES256(), key)`. Never trust the `alg` from the incoming header alone. 3. **Never use `jwt.ParseInsecure` for tokens received from the network.** It is for testing or for extracting claims from a token whose origin is already trusted by other means. 4. **`jwa` algorithms are functions in v4, not constants.** Write `jwa.RS256()`, not `jwa.RS256`. This trips up users migrating from v2/v3. 5. **`kid` matching is enforced when verifying with a JWK Set.** Override the requirement with `jwt.WithKeySet(set, jws.WithRequireKid(false))` only when you understand the consequences. 6. **`jku` (key URL in the JWS header) is attacker-controlled.** Use `jwt.WithVerifyAuto` only with a `jwkfetch.Client` configured with a `jwkfetch.NewMapWhitelist()` of allowed URLs. 7. **HMAC keys are `[]byte`, not `string`.** Pass `[]byte("secret")`, or better, a `jwk.Key` imported from those bytes. 8. **`jwk.Import` and `jwk.Export` require explicit type parameters.** `jwk.Import[jwk.Key](raw)`, `jwk.Export[*rsa.PublicKey](key)`. Their type argument is not inferable from the call, so bare `jwk.Import(raw)` does **not** compile. 9. **`jwk.ParseKey` is not generic.** `jwk.ParseKey(data)` returns `(jwk.Key, error)`. Use `jwk.ParseKeyAs[jwk.RSAPublicKey](data)` when a concrete JWK type is required. ## Verifying a JWT (the 90% case) ```go import ( "github.com/lestrrat-go/jwx/v4/jwa" "github.com/lestrrat-go/jwx/v4/jwt" ) tok, err := jwt.Parse(raw, jwt.WithKey(jwa.RS256(), publicKey)) if err != nil { // signature failed, claim validation failed, or parse failed return err } // tok is verified + validated; safe to read claims ``` `publicKey` may be: - a `*rsa.PublicKey` / `*ecdsa.PublicKey` / `ed25519.PublicKey` from `crypto/*` - `[]byte` for HMAC algorithms - a `jwk.Key` To validate against expected claim values, pass `jwt.WithIssuer(...)`, `jwt.WithAudience(...)`, `jwt.WithSubject(...)`, `jwt.WithJwtID(...)`. To extend the default clock skew window: `jwt.WithAcceptableSkew(30 * time.Second)`. ### Verifying with a JWK Set (kid-based key selection) ```go set, err := jwk.Parse(jwksBytes) if err != nil { return err } tok, err := jwt.Parse(raw, jwt.WithKeySet(set)) ``` If the JWS header has a `kid`, the matching key is selected from the set. The algorithm comes from each key's `alg` field. To opt out of kid-required matching: `jwt.WithKeySet(set, jws.WithRequireKid(false))`. **A key with no `alg` field is skipped, not guessed at.** Inference from the key type is opt-in via `jwt.WithKeySet(set, jws.WithInferAlgorithmFromKey(true))`, and it is a fallback, not a default. It tries every algorithm compatible with the key type, so it is slower and weaker than an explicit `alg`; combined with `jws.WithRequireKid(false)` against a large JWKS it also multiplies out to `N_keys × N_algs_per_keytype` verification attempts. The right fix is almost always to add `alg` to the keys in the JWKS. If a user reports that verification against a JWKS silently finds no usable key, check for missing `alg` fields first. ### Verifying via JWKS endpoint HTTP fetching is **not** in the core `jwk` package in v4. It lives in a companion module: ```go import "github.com/jwx-go/jwkfetch/v4" client := jwkfetch.NewClient() set, err := client.Fetch(ctx, "https://issuer.example/jwks.json") // then: jwt.Parse(raw, jwt.WithKeySet(set)) ``` For repeated fetches with background refresh, use `jwkfetch.NewCache`. For `jku`-driven verification, build a `jwkfetch.Client` with a `jwkfetch.NewMapWhitelist()` of allowed URLs and pass it to `jwt.WithVerifyAuto(client)`. ## Signing a JWT ```go tok, err := jwt.NewBuilder(). Issuer("https://issuer.example"). Audience([]string{"https://api.example"}). Subject("user-123"). IssuedAt(time.Now()). Expiration(time.Now().Add(15 * time.Minute)). Claim("scope", "read:things"). Build() if err != nil { return err } signed, err := jwt.Sign(tok, jwt.WithKey(jwa.RS256(), privateKey)) ``` `privateKey` may be a `*rsa.PrivateKey`/`*ecdsa.PrivateKey`/`ed25519.PrivateKey`/HMAC `[]byte`, or a `jwk.Key`. Match algorithm to key type: | Family | Use when | |--------|----------| | `HS256` / `HS384` / `HS512` | Shared-secret (HMAC). Same secret signs and verifies. | | `RS256` / `RS384` / `RS512` | RSA, widest interop. | | `PS256` / `PS384` / `PS512` | RSA-PSS, prefer over `RS*` for new systems. | | `ES256` / `ES384` / `ES512` | ECDSA, smaller signatures than RSA. | | `Ed25519` (`jwa.EdDSAEd25519()`) | Ed25519, fastest verify; preferred for new systems where supported. | | `EdDSA` (`jwa.EdDSA()`) | The pre-RFC-9864 polymorphic identifier. **Deprecated.** Use it only to interoperate with a producer or consumer that still emits or expects `alg: EdDSA`. | | `none` | **Never.** jwx refuses by default. | ## JWK basics `jwk.Import` and `jwk.Export` require the type parameter. The parsers do not: `jwk.ParseKey` and `jwk.Parse` are non-generic, and `jwk.ParseKeyAs[T]` is the typed variant. ```go // Parse a single JWK (returns jwk.Key): key, err := jwk.ParseKey(jwkBytes) // Parse a single JWK with a concrete type (fails if not that type): rsaKey, err := jwk.ParseKeyAs[jwk.RSAPublicKey](jwkBytes) // Parse a JWK Set (returns jwk.Set, not generic): set, err := jwk.Parse(jwksBytes) // Wrap an existing crypto.* key as a jwk.Key: key, err := jwk.Import[jwk.Key](rsaPrivKey) // Or with a concrete jwk type: typed, err := jwk.Import[jwk.RSAPrivateKey](rsaPrivKey) // Export a jwk.Key back to a crypto.* key: raw, err := jwk.Export[*rsa.PublicKey](key) ``` `jwk.Import[jwk.Key]` is the default. Use a concrete type parameter (`jwk.RSAPrivateKey`, `jwk.ECDSAPublicKey`, `jwk.SymmetricKey`, `jwk.OKPPublicKey`, etc.) only when you want compile-time guarantees and acceptance of failure when the input is anything else. ### Generating keys ```go import ( "crypto/rand" "crypto/rsa" "github.com/lestrrat-go/jwx/v4/jwa" "github.com/lestrrat-go/jwx/v4/jwk" ) raw, _ := rsa.GenerateKey(rand.Reader, 2048) key, _ := jwk.Import[jwk.Key](raw) key.Set(jwk.KeyIDKey, "2025-q1") key.Set(jwk.AlgorithmKey, jwa.RS256()) pubKey, _ := jwk.PublicKeyOf(key) ``` `jwk.KeyIDKey` and `jwk.AlgorithmKey` are string constants for the standard JWK fields `kid` and `alg`. ## JWS (signing arbitrary payloads) ```go sig, err := jws.Sign(payload, jws.WithKey(jwa.ES256(), privateKey)) payload, err := jws.Verify(sig, jws.WithKey(jwa.ES256(), publicKey)) ``` `jws.Parse` only parses the structure — it does **not** verify. Use `jws.Verify` (which returns the verified payload) for verification. ### The protected `alg` must match the verifying algorithm exactly `jws.Verify` rejects a message whose protected header advertises one algorithm while it is verified under another. The comparison is plain string equality with no aliasing, and it applies to every key source (`jws.WithKey`, `jws.WithKeySet`, `jws.WithVerifyAuto`, custom `jws.WithKeyProvider`). The check only fires when the protected header actually carries an `alg`. The practical consequence involves EdDSA. Per RFC 9864, `EdDSA`, `Ed25519` and `Ed448` are three distinct `alg` values, so a token whose header says `alg: Ed25519` does **not** verify under `jws.WithKey(jwa.EdDSA(), key)`, and vice versa. Match the identifier the producer actually emitted. `jws.WithSkipAlgorithmMatch(true)` bypasses the check. It exists for interop with non-conforming producers, and it weakens a real safety guard, so treat it the way you treat `jws.WithRequireKid(false)`. ## JWE (encrypting payloads) ```go enc, err := jwe.Encrypt( payload, jwe.WithKey(jwa.RSA_OAEP_256(), recipientPublicKey), jwe.WithContentEncryption(jwa.A256GCM()), ) plain, err := jwe.Decrypt(enc, jwe.WithKey(jwa.RSA_OAEP_256(), recipientPrivateKey)) ``` `jwe.WithKey(alg, key)` is the same on both encrypt and decrypt sides — this symmetry is intentional. ## Companion modules Beyond the core `github.com/lestrrat-go/jwx/v4` module, the project ships companion modules under `github.com/jwx-go`. The agent should know **what's available and when to reach for each one** — depth lives in each module's godoc. For algorithm and HPKE modules: **import for side effects** (`import _ "..."`). They register themselves in `init()` and panic at import time if registration fails (intentional — surfaces problems early). The one case that used to panic in normal use no longer does: see the ML-DSA note below. ### Signature algorithms (extension) | Module | What it enables | When to use |
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen