### Install jwx Command Line Tool Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/cmd/jwx/README.md Install the jwx tool by cloning the repository, navigating to the directory, and running the make command. The tool will be installed in your GOBIN or GOPATH/bin. ```shell git clone https://github.com/lestrrat-go/jwx.git cd jwx make jwx ``` -------------------------------- ### Install lestrrat-go/jwx/v4 Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/README.md Use this command to install the v4 of the jwx library. ```bash go get github.com/lestrrat-go/jwx/v4 ``` -------------------------------- ### Install jwxmigrate tool Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/MIGRATION.md Install the jwxmigrate tool to assist with the migration process. This command fetches the latest version of the tool. ```bash go install github.com/jwx-go/jwxmigrate/v4@latest ``` -------------------------------- ### Complete JWS/JWT/JWE/JWK Operations Example Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/README.md This comprehensive example demonstrates parsing JWKs, working with JWTs (building, signing, verifying, and parsing from HTTP requests), encrypting and decrypting arbitrary payloads with JWE, and signing and verifying arbitrary payloads with JWS. It requires JWK private and public keys, and sample payload data. ```Go 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: } ``` -------------------------------- ### Unified Binary Subcommands Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/design/v4-codegen-unified-binary.md Example usage of the new unified `jwxcodegen` binary, demonstrating its subcommands for various generation tasks. ```bash jwxcodegen generate-jwa -objects=path/to/objects.yml jwxcodegen generate-headers -objects=path/to/jws-objects.yml jwxcodegen generate-jwk -objects=path/to/objects.yml jwxcodegen generate-jwt -objects=path/to/objects.yml jwxcodegen generate-options -config=path/to/options.yaml jwxcodegen generate-readfile ``` -------------------------------- ### v4 Custom Key Importer Registration Example Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/design/v4-generics.md Demonstrates the v4 generic approach for registering a custom key importer, passing a typed function directly. ```go // v4: pass a KeyImporter[T]; T inferred from the adapter's typed Import. jwk.RegisterKeyImporter(jwk.KeyImportFunc[*myKey](func(k *myKey) (jwk.Key, error) { return doImport(k) })) ``` -------------------------------- ### v3 Field Access Example Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/design/v4-generics.md Shows the v3 method for retrieving a field value from a key object using a pointer to a variable. ```go // v3: field access var kid string er := key.Get(jwk.KeyIDKey, &kid) ``` -------------------------------- ### Include Example File Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/CLAUDE.md This markdown comment is used to include example code from a specified file into the documentation. It is processed by the 'scripts/autodoc.pl' script. ```markdown ``` -------------------------------- ### Example options.yaml Entry Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/CLAUDE.md This YAML entry defines an option for token specification, which is used to generate Go code for the functional options pattern. ```yaml options: - ident: Token interface: ParseOption argument_type: Token comment: | WithToken specifies the token instance... ``` -------------------------------- ### Example YAML Configuration for Options Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/design/v4-codegen-minor-generators.md This YAML snippet demonstrates how to configure options, including a special case for overriding the identifier name used in `ident.String()`. ```yaml options: - ident: Compact option_name: WithCompact ident_name: WithSerialization # used for ident.String() instead of option_name constant_value: fmtCompact interface: SerializeOption ``` -------------------------------- ### Parse and Verify JWT using jku Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/01-jwt.md This example shows how to create a JWT, sign it with a key, and then parse and verify it using the JWK Set URL specified in the `jku` header. It highlights the importance of using a JWK Fetcher with a whitelist to prevent SSRF attacks. ```go package examples_test import ( "crypto/rand" "crypto/rsa" "encoding/json" "fmt" "net/http" "net/http/httptest" "github.com/jwx-go/jwkfetch/v4" "github.com/lestrrat-go/jwx/v4/jwa" "github.com/lestrrat-go/jwx/v4/jwk" "github.com/lestrrat-go/jwx/v4/jws" "github.com/lestrrat-go/jwx/v4/jwt" ) func Example_jwt_parse_with_jku() { set := jwk.NewSet() var signingKey jwk.Key // for _, alg := range algorithms { for i := 0; i < 3; i++ { pk, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to generate private key: %s\n", err) return } // too lazy to write a proper algorithm. just assign every // time, and signingKey will end up being the last key generated privkey, err := jwk.Import[jwk.Key](pk) if err != nil { fmt.Printf("failed to create jwk.Key: %s\n", err) return } privkey.Set(jwk.KeyIDKey, fmt.Sprintf(`key-%d`, i)) // It is important that we are using jwk.Key here instead of // rsa.PrivateKey, because this way `kid` is automatically // assigned when we sign the token signingKey = privkey pubkey, err := privkey.PublicKey() if err != nil { fmt.Printf("failed to create public key: %s\n", err) return } set.AddKey(pubkey) } srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(set) })) defer srv.Close() // Create a JWT token := jwt.New() token.Set(`foo`, `bar`) hdrs := jws.NewHeaders() hdrs.Set(jws.JWKSetURLKey, srv.URL) serialized, err := jwt.Sign(token, jwt.WithKey(jwa.RS256(), signingKey, jws.WithProtectedHeaders(hdrs))) if err != nil { fmt.Printf("failed to seign token: %s\n", err) return } // jku verification uses a jwk.Fetcher to retrieve the JWKS // referenced in the JWS protected header. Use jwkfetch.Client — // it is the canonical implementation and the one this option is // designed around. // // IMPORTANT: the `jku` URL comes from the JWS protected header, // which is untrusted input. A real application MUST pass // jwkfetch.WithWhitelist with a MapWhitelist / RegexpWhitelist // restricted to its known issuer set — otherwise a hostile peer // can point the fetcher at any URL it can reach (SSRF) and have // its own keys accepted as "the issuer's keys". This example uses // srv.URL as a "known issuer" because httptest picks a random // port each run. // // If you use a RegexpWhitelist instead of a MapWhitelist, anchor // the pattern — e.g. `^https://issuer\.example/` — because patterns // are NOT anchored for you, and an unanchored pattern silently // allows look-alike hosts like issuer.example.attacker.com. client := jwkfetch.NewClient( // httptest serves HTTPS with a self-signed cert, so the // Client needs srv.Client() to validate it. jwkfetch.WithHTTPClient(srv.Client()), jwkfetch.WithWhitelist(jwkfetch.NewMapWhitelist().Add(srv.URL)), ) tok, err := jwt.Parse(serialized, jwt.WithVerifyAuto(client)) if err != nil { fmt.Printf("failed to verify token: %s\n", err) return } _ = tok // OUTPUT: } ``` -------------------------------- ### v3 Custom Key Importer Registration Example Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/design/v4-generics.md Illustrates the v3 approach for registering a custom key importer, involving a type assertion within the import function. ```go // v3: register custom key importer jwk.RegisterKeyImporter(&myKey{}, jwk.KeyImportFunc(func(src any) (jwk.Key, error) { k, ok := src.(*myKey) if !ok { return nil, fmt.Errorf("wrong type") } return doImport(k) })) ``` -------------------------------- ### Verify Skill Cache Against Source Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/agents/plugin-tests/SELF-TEST.md Compares the installed skill's cache file with the source file to ensure they match. No output indicates a successful match. ```bash diff -q agents/plugin/skills/guide/SKILL.md \ ~/.claude/plugins/cache/jwx-v4/jwx-dev-v4//skills/guide/SKILL.md ``` -------------------------------- ### Type-Safe Field Access with Generics (v3 vs v4) Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/Changes-v4.md Illustrates the transition from manual field retrieval using Get to type-safe generic Get[T] in v4. ```go // v3 var kid string key.Get("kid", &kid) // v4 kid, err := jwk.Get[string](key, "kid") ``` -------------------------------- ### v4 Field Access Example Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/design/v4-generics.md Illustrates the v4 generic method for retrieving a field value from a key object, returning the value directly. ```go // v4: generic accessor kid, err := jwk.Get[string](key, jwk.KeyIDKey) ``` -------------------------------- ### Advanced JWE Header Filtering Example Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/03-jwe.md Demonstrates advanced JWE HeaderFilter functionality with security filtering, service integration scenarios, and header manipulation. Use this for complex filtering requirements. ```go package examples_test import ( "fmt" "github.com/jwx-go/jwxfilter/v4/jwefilter" "github.com/lestrrat-go/jwx/v4/jwa" "github.com/lestrrat-go/jwx/v4/jwe" ) // Example_jwe_filter_advanced demonstrates advanced JWE HeaderFilter functionality // with security filtering, service integration scenarios, and header manipulation. func Example_jwe_filter_advanced() { // Create JWE headers with comprehensive metadata including security and service information protectedHeaders := jwe.NewHeaders() protectedHeaders.Set(jwe.AlgorithmKey, jwa.RSA_OAEP_256()) protectedHeaders.Set(jwe.ContentEncryptionKey, jwa.A256GCM()) protectedHeaders.Set(jwe.ContentTypeKey, "application/json") protectedHeaders.Set(jwe.KeyIDKey, "service-key-001") // Security headers protectedHeaders.Set("security_level", "high") protectedHeaders.Set("access_control", "restricted") protectedHeaders.Set("encryption_version", "v2.1") // Service integration headers protectedHeaders.Set("service_name", "user-service") protectedHeaders.Set("api_version", "v1.2.3") protectedHeaders.Set("request_id", "req-789abc") protectedHeaders.Set("correlation_id", "corr-456def") // Operational headers protectedHeaders.Set("environment", "production") protectedHeaders.Set("region", "us-east-1") protectedHeaders.Set("trace_id", "trace-123xyz") // Use the headers directly for filtering examples headers := protectedHeaders // Advanced Example 1: Service Integration - Filter service-related headers serviceFilter := jwefilter.ByName("service_name", "api_version", "request_id", "correlation_id", jwe.KeyIDKey) serviceHeaders, err := serviceFilter.Filter(headers) if err != nil { fmt.Printf("Failed to filter service headers: %s\n", err) return } // Advanced Example 2: Security Headers - Filter security-related metadata securityFilter := jwefilter.ByName("security_level", "access_control", "encryption_version", jwe.AlgorithmKey, jwe.ContentEncryptionKey) securityHeaders, err := securityFilter.Filter(headers) if err != nil { fmt.Printf("Failed to filter security headers: %s\n", err) return } // Advanced Example 3: Operational Headers - Filter operational metadata operationalFilter := jwefilter.ByName("environment", "region", "trace_id") operationalHeaders, err := operationalFilter.Filter(headers) if err != nil { fmt.Printf("Failed to filter operational headers: %s\n", err) return } // Use operationalHeaders variable by checking its length if len(operationalHeaders.Keys()) == 0 { fmt.Printf("No operational headers found\n") return } // Advanced Example 4: Public Headers - Remove sensitive headers for public APIs sensitiveFilter := jwefilter.ByName("security_level", "access_control", "encryption_version", "trace_id") publicHeaders, err := sensitiveFilter.Reject(headers) if err != nil { fmt.Printf("Failed to create public headers: %s\n", err) return } // Use publicHeaders variable by checking its length if len(publicHeaders.Keys()) == 0 { fmt.Printf("No public headers found\n") return } // Advanced Example 5: Minimal Headers - Keep only essential headers for bandwidth optimization essentialFilter := jwefilter.ByName(jwe.AlgorithmKey, jwe.ContentEncryptionKey, jwe.KeyIDKey) minimalHeaders, err := essentialFilter.Filter(headers) if err != nil { fmt.Printf("Failed to filter minimal headers: %s\n", err) return } // Use minimalHeaders variable by checking its length if len(minimalHeaders.Keys()) == 0 { fmt.Printf("No minimal headers found\n") return } // Advanced Example 6: Custom Validation - Filter headers based on security requirements isValidSecurityLevel := validateJWESecurityHeaders(securityHeaders) if !isValidSecurityLevel { fmt.Printf("Security validation failed\n") return } isValidServiceConfig := validateJWEServiceHeaders(serviceHeaders) if !isValidServiceConfig { fmt.Printf("Service configuration validation failed\n") return } // Advanced Example 7: Header transformation for different environments prodHeaders := createJWEEnvironmentHeaders(headers, "production") if len(prodHeaders.Keys()) == 0 { fmt.Printf("Failed to create production headers\n") return } testHeaders := createJWEEnvironmentHeaders(headers, "testing") if len(testHeaders.Keys()) == 0 { fmt.Printf("Failed to create testing headers\n") return } // OUTPUT: } ``` -------------------------------- ### Parse JWT from Filesystem Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/01-jwt.md Parses a JWT token directly from a file on the filesystem. This example demonstrates loading the JWT from a temporary file and shows how to disable verification and validation for demonstration purposes, though production code should always validate. ```go package examples_test import ( "fmt" "os" "path/filepath" "github.com/lestrrat-go/jwx/v4/jwt" ) func Example_jwt_ParseFS() { f, err := os.CreateTemp(``, `jwt_parsefs-*.jws`) if err != nil { fmt.Printf("failed to create temporary file: %s\n", err) return } defer os.Remove(f.Name()) fmt.Fprint(f, exampleJWTSignedHMAC) f.Close() // This example calls ParseFS with both jwt.WithVerify(false) and // jwt.WithValidate(false) only because there is no key context // here — it demonstrates the FS-loading mechanics, nothing more. // Production code reading a JWT from any source MUST pass // jwt.WithKey() / jwt.WithKeySet() and MUST NOT disable // jwt.WithValidate. The library exposes jwt.ParseInsecure for the // inspect-without-verifying path when consuming raw bytes // directly; ParseFS has no corresponding ParseFSInsecure today, // so the two-option chant is the explicit way to express the // same intent here. tok, err := jwt.ParseFS(os.DirFS(filepath.Dir(f.Name())), filepath.Base(f.Name()), jwt.WithVerify(false), jwt.WithValidate(false)) if err != nil { fmt.Printf("failed to read file %q: %s\n", f.Name(), err) return } _ = tok // OUTPUT: } ``` -------------------------------- ### Fetch and Use JWK Set Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/jwk/README.md Fetches a JWK set from a given URL, iterates through the keys, and demonstrates conversion between JWK format and raw key types. It includes setup for using a local test server or fetching live from Google's JWKS endpoint. ```go package examples_test import ( "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "os" "strings" "github.com/jwx-go/jwkfetch/v4" "github.com/lestrrat-go/jwx/v4/jwk" ) // googleJWKSURL is the canonical OAuth 2.0 / OpenID Connect JWKS // endpoint for accounts.google.com. Real production code calling // jwkfetch against Google would pass this string verbatim. const googleJWKSURL = "https://www.googleapis.com/oauth2/v3/certs" // googleJWKSFixture is a small inline JWK Set used by the example // when running offline (the default). It mirrors the shape Google // returns — two RSA public keys with kid + alg — but the key // material is fake. const googleJWKSFixture = `{ "keys":[ {"kty":"RSA", "n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "alg":"RS256", "kid":"example-key-1"}, {"kty":"RSA", "n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "alg":"RS256", "kid":"example-key-2"} ] } ``` -------------------------------- ### Basic JWS Header Filtering Example Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/02-jws.md Demonstrates how to use jwsfilter.ByName and jwsfilter.Standard to filter JWS headers. It shows how to keep custom headers, keep standard headers, and reject specific sensitive headers. ```go package examples_test import ( "fmt" "github.com/jwx-go/jwxfilter/v4/jwsfilter" "github.com/lestrrat-go/jwx/v4/jwa" "github.com/lestrrat-go/jwx/v4/jwk" "github.com/lestrrat-go/jwx/v4/jws" ) func Example_jws_header_filter_basic() { key, err := jwk.Import[jwk.Key]([]byte(`my-secret-key`)) if err != nil { fmt.Printf("failed to create key: %s\n", err) return } // Build JWS headers with both RFC 7515 standard fields and custom ones. headers := jws.NewHeaders() headers.Set(jws.AlgorithmKey, jwa.HS256()) headers.Set(jws.KeyIDKey, "key-2024") headers.Set(jws.TypeKey, "JWT") headers.Set("custom-claim", "important-data") headers.Set("app-version", "v1.2.3") headers.Set("environment", "production") payload := []byte(`{"user": "alice", "role": "admin"}`) signed, err := jws.Sign(payload, jws.WithKey(jwa.HS256(), key, jws.WithProtectedHeaders(headers))) if err != nil { fmt.Printf("failed to sign: %s\n", err) return } msg, err := jws.Parse(signed) if err != nil { fmt.Printf("failed to parse: %s\n", err) return } originalHeaders := msg.Signatures()[0].ProtectedHeaders() // Filters were extracted out of core into github.com/jwx-go/jwxfilter/v4. // jwsfilter.ByName builds a filter over jws.Headers for the given // field names; Filter keeps the match, Reject removes it. Each call // returns a fresh jws.Headers, so the original is left untouched. customFilter := jwsfilter.ByName("custom-claim", "app-version", "environment") if _, err = customFilter.Filter(originalHeaders); err != nil { fmt.Printf("failed to filter custom headers: %s\n", err) return } // jwsfilter.Standard() is the preset for the 11 RFC 7515 headers // (alg, cty, crit, jwk, jku, kid, typ, x5c, x5t, x5t#S256, x5u). if _, err = jwsfilter.Standard().Filter(originalHeaders); err != nil { fmt.Printf("failed to filter standard headers: %s\n", err) return } // Reject removes the named fields and keeps everything else — useful // for scrubbing a specific sensitive extension header before logging. sensitiveFilter := jwsfilter.ByName("custom-claim") if _, err = sensitiveFilter.Reject(originalHeaders); err != nil { fmt.Printf("failed to reject sensitive headers: %s\n", err) return } // OUTPUT: } ``` -------------------------------- ### Callback-based Option Example Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/99-faq.md Illustrates a callback-based option pattern where options modify an object directly. This approach requires specific callback signatures and can expose internal object details. ```go func MyOption(obj *Object) { obj.FieldA = ... obj.FieldB = ... } ``` -------------------------------- ### Go Generate Directive Example Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/design/v4-codegen-unified-binary.md This directive is used in Go source files to trigger code generation. It now points to a unified shell script that invokes the new `jwxcodegen` binary. ```go //go:generate ../tools/cmd/genjws.sh ``` -------------------------------- ### Go Program Build and Test Recipe Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/agents/plugin-tests/SELF-TEST.md This bash script outlines the steps for setting up a temporary directory, initializing a Go module, writing a main Go program, tidying dependencies with JSONv2 experiment enabled, and building the program. The test passes only if 'go build' exits with a status code of 0. ```bash WORKDIR="$(mktemp -d)" cd "$WORKDIR" go mod init example.com/jwx-self-test/case-N # write main.go with all required imports + func main() so it links GOEXPERIMENT=jsonv2 go mod tidy GOEXPERIMENT=jsonv2 go build ./... ``` -------------------------------- ### Install Claude Code Skill for jwx-dev-v4 Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/README.md Install the jwx-dev-v4 skill for Claude Code to get assistance with using the jwx library. This skill is specific to v4. ```bash /plugin marketplace add lestrrat-go/jwx /plugin install jwx-dev-v4 ``` -------------------------------- ### Update Field Access from Get to Field/Get[T] Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/MIGRATION.md Migrate from the output parameter pattern of `Get(name string, dst any) error` to the preferred generic accessor `Get[T](key, name)` or `Field()` for existence checks. ```go // Before: output parameter pattern var kid string if err := key.Get("kid", &kid); err != nil { return err } var exp time.Time if err := token.Get(jwt.ExpirationKey, &exp); err != nil { return err } ``` ```go // After: generic accessor (preferred) kid, err := jwk.Get[string](key, "kid") if err != nil { return err } exp, err := jwt.Get[time.Time](token, jwt.ExpirationKey) if err != nil { return err } ``` ```go // After: Field() for simple existence checks if v, ok := key.Field("kid"); ok { kid := v.(string) // ... } ``` -------------------------------- ### Companion Module Registration Failure Handling Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/agents/docs/internals.md Demonstrates the pattern for companion modules to register extensions in init(), panicking on registration failure to surface issues immediately. ```go func init() { if err := jws.RegisterSigner(alg, mySigner{}); err != nil { panic(fmt.Sprintf("jwx-go/: failed to register signer: %s", err)) } } ``` -------------------------------- ### Generate JWK Field and Get Methods Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/design/v4-codegen-genjwk.md Emits Field(name) (any, bool) and Get(name, dst) error methods for JWK objects. Field() is the interface-level accessor, and Get() is a struct-level convenience wrapper. ```go func generateKeyFieldAndGet(o *codegen.Output, kt *KeyType, obj *codegen.Object) { // ~40 lines: Field() is the interface-level accessor (can use shared GenerateFieldCases) // Get() is a struct-level convenience wrapper around Field + blackmagic.AssignIfCompatible } ``` -------------------------------- ### JWK Key Creation and Algorithm Handling Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/Changes-v2.md Shows how to create JWK keys from raw data and how to use the Algorithm() method. It also covers the transition from jwk.AutoRefresh to jwk.Cache. ```go // jwk.New() was confusing. Renamed to fit the actual implementation key, err := jwk.FromRaw(rawKey) ``` ```go // Algorithm() now returns jwa.KeyAlgorithm type. `jws.Sign()` // and other function that receive JWK algorithm names accept // this new type, so you can use the same key and do the following // (previously you needed to type assert) jws.Sign(payload, jws.WithKey(key.Algorithm(), key)) ``` ```go // If you need the specific type, type assert key.Algorithm().(jwa.SignatureAlgorithm) ``` ```go // jwk.AutoRefresh is no more. Use jwk.Cache cache := jwk.NewCache(ctx, options...) ``` ```go // Certificate chains are no longer jwk.CertificateChain type, but // *(github.com/lestrrat-go/jwx/cert).Chain cc := key.X509CertChain() // this is *cert.Chain now ``` -------------------------------- ### Install and Uninstall jwx-dev-v4 Skill Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/agents/plugin-tests/SELF-TEST.md Commands to uninstall a previous version and install the latest jwx-dev-v4 skill, followed by a plugin reload. ```bash /plugin uninstall jwx-dev-v4@jwx-v4 /plugin install jwx-dev-v4@jwx-v4 /reload-plugins ``` -------------------------------- ### Example JWE Message Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/00-anatomy.md This is an example of a JWE message in its compact serialization format, consisting of five base64 encoded strings separated by periods. ```text eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTE5MkdDTSIsImVwayI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IndMckhLNnBTLXZzdmhQZUNfNTN0ZWpxYzZIZUFsMllRWDRmY1hPNGV1bmciLCJ5IjoiV2V3bFdKazJ4QWJYSXE3WFJ6aVlZa2lxMjJfOF9TQ0VsbTA1Vm1iUGhFWSJ9fQ..7UTcbVpz-Ed1Q0wq.sneVfeTeAvzZNSMGpQ.JNo1BbDaKB-Q1mWaBNmdow ``` -------------------------------- ### Import Algorithm Extension Modules Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/MIGRATION.md Algorithm support like ES256K, Ed448, and assembly base64 are now managed via extension modules. Import the relevant module to enable them. Use a blank import if only registration is needed. ```go // After: import the extension module (registers via init()) import "github.com/jwx-go/es256k/v4" // Use es256k.ES256K() instead of jwa.ES256K() // Use es256k.Secp256k1() instead of jwa.Secp256k1() ``` ```go import _ "github.com/jwx-go/es256k/v4" ``` ```go import "github.com/jwx-go/ed448/v4" // ed448.EdDSAEd448(), ed448.Curve() import _ "github.com/jwx-go/asmbase64/v4" // registration only ``` -------------------------------- ### Example Compact JWS Message Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/00-anatomy.md This is an example of a JWS message in its compact serialization format, consisting of three base64 encoded strings separated by periods. ```text eyJhbGciOiJFUzI1NiJ9.SGVsbG8sIFdvcmxkCg.3q5N5JyFphiJolUZuBuUZhuWDfmLDR__rZe3lnuaxWe3bfrfvJS9HmUUhie56NqkyN7vjOl8hm6tzJKTc2oNsg ``` -------------------------------- ### Get JWT Field Value Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/01-jwt.md Use the Get method to retrieve any field from a JWT token. The value is returned as an interface{}, and a boolean indicates if the field exists. ```go var v interface{} // can be concrete type, if you know the type beforehand err := token.Get(name, &v) ``` -------------------------------- ### Type-Safe Field Access with Generics (jwk) Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/Changes-v4.md Use generic Get[T] for type-safe retrieval of fields from JWK keys. This replaces the older Get method which required manual type assertion. ```go kid, err := jwk.Get[string](key, "kid") ``` -------------------------------- ### Override Fuzz Test Duration Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/agents/docs/testing.md Run all fuzz targets with a custom duration, for example, 5 minutes. ```bash FUZZTIME=5m make fuzz ``` -------------------------------- ### Configure JWK Fetcher with Whitelist Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/02-jws.md Instantiate a JWK fetcher client with a whitelist to restrict reachable JWK Set URLs. This is crucial for security when verifying JWS with `jku`. ```go import "github.com/jwx-go/jwkfetch/v4" fetcher := jwkfetch.NewClient( jwkfetch.WithWhitelist( jwkfetch.NewMapWhitelist().Add(`https://issuer.example/jwks.json`), ), ) ``` -------------------------------- ### Get Predefined JWT Fields Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/01-jwt.md Convenience methods are available for accessing predefined JWT fields like Subject, Issuer, and NotBefore. ```go s := token.Subject() s := token.Issuer() t := token.NotBefore() ``` -------------------------------- ### v4 Generic Field Accessor Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/design/v4-generics.md v4 introduces generic `Get` functions (e.g., `jwt.Get[T]`) for type-safe retrieval of token claims. ```go issuer, err := jwt.Get[string](token, jwt.IssuerKey) custom, err := jwt.Get[MyType](token, "my-custom-claim") ``` -------------------------------- ### Test All Companion Modules Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/agents/docs/companions.md Runs Go tests with race detection against all companion modules using a local jwx checkout. Clones are managed in `.companions/repo/`. ```bash ./scripts/test-companion.sh ``` -------------------------------- ### Sign and Verify with ML-DSA Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/10-extensions.md Demonstrates how to generate an ML-DSA key pair, sign a payload, and verify the signature using jwx. Ensure the correct ML-DSA parameter set is used for both signing and verification. ```go package examples_test import ( "fmt" "filippo.io/mldsa" jwxmldsa "github.com/jwx-go/mldsa/v4" "github.com/lestrrat-go/jwx/v4/jws" ) func Example_mldsa_sign_verify() { // ML-DSA is a post-quantum digital signature scheme (FIPS 204). // To use it with jwx, import github.com/jwx-go/mldsa/v4 for its // side effects — the init() function registers ML-DSA algorithms, // key importers/exporters, and JWS signers/verifiers automatically. // Generate an ML-DSA-65 key pair. ML-DSA comes in three parameter sets: // - ML-DSA-44 (NIST Level 2, smallest/fastest) // - ML-DSA-65 (NIST Level 3, balanced) // - ML-DSA-87 (NIST Level 5, highest security) // Each parameter set determines the key and signature sizes. // mldsa.GenerateKey takes a *mldsa.Parameters to select the variant. sk, err := mldsa.GenerateKey(mldsa.MLDSA65()) if err != nil { fmt.Printf("failed to generate ML-DSA key: %s\n", err) return } payload := []byte("Hello, post-quantum world!") // jws.Sign accepts raw *mldsa.PrivateKey directly — the mldsa package's // init() registers a JWS signer that handles the conversion internally. // The algorithm (jwxmldsa.MLDSA65()) must match the key's parameter set; // using a mismatched algorithm (e.g., MLDSA44() with an ML-DSA-65 key) // will fail. signed, err := jws.Sign(payload, jws.WithKey(jwxmldsa.MLDSA65(), sk)) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } // Verification uses the public key extracted from the private key via // PublicKey(). Like signing, raw *mldsa.PublicKey is accepted directly. // You could also pass the private key itself — the verifier extracts // the public key internally. verified, err := jws.Verify(signed, jws.WithKey(jwxmldsa.MLDSA65(), sk.PublicKey())) if err != nil { fmt.Printf("failed to verify signature: %s\n", err) return } fmt.Printf("%s\n", verified) // OUTPUT: // Hello, post-quantum world! } ``` -------------------------------- ### Encode a jwk.Set using jwkbb.EncodePEM Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/MIGRATION.md To encode a jwk.Set, iterate through its keys using jwk.ExportAll[any] to get raw keys, then pass them variadically to jwkbb.EncodePEM. ```go jwkbb.EncodePEM(jwk.ExportAll[any](set)...) ``` -------------------------------- ### Configuring JWK Fetcher for jws.Verify in v4 Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/MIGRATION.md Shows how to correctly integrate a JWK fetcher with `jws.Verify` in v4, especially when using the `jku` header. It emphasizes building the fetcher with appropriate whitelists and HTTP clients once, then passing it to `jws.WithVerifyAuto`. ```go // Before — wiring into jws.Verify via jku _, err := jws.Verify(signed, jws.WithVerifyAuto(nil, jwk.WithFetchWhitelist(wl), jwk.WithHTTPClient(c))) // After — build the fetcher once, pass it fetcher := jwkfetch.NewClient( jwkfetch.WithWhitelist(wl), jwkfetch.WithHTTPClient(c), ) _, err := jws.Verify(signed, jws.WithVerifyAuto(fetcher)) ``` -------------------------------- ### Using Plain Struct for JWT Processing Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/01-jwt.md Demonstrates how to build, sign, verify, and unmarshal a JWT into a plain struct for performance. Use this when performance is critical and you want to avoid extra abstraction layers. ```go package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v4/jwa" "github.com/lestrrat-go/jwx/v4/jws" "github.com/lestrrat-go/jwx/v4/jwt" ) func Example_jwt_plain_struct() { t1, err := jwt.NewBuilder(). Issuer("https://github.com/lestrrat-go/jwx/v4/examples"). Subject("raw_struct"). Claim("private", "foobar"). Build() if err != nil { fmt.Fprintf(os.Stderr, "failed to build JWT: %s\n", err) } key := []byte("secret") signed, err := jwt.Sign(t1, jwt.WithKey(jwa.HS256(), key)) if err != nil { fmt.Printf("failed to sign JWT: %s\n", err) } rawJWT, err := jws.Verify(signed, jws.WithKey(jwa.HS256(), key)) if err != nil { fmt.Printf("failed to verify JWS: %s\n", err) } type MyToken struct { Issuer string `json:"iss"` Subject string `json:"sub"` Private string `json:"private"` } var t2 MyToken if err := json.Unmarshal(rawJWT, &t2); err != nil { fmt.Printf("failed to unmarshal JWT: %s\n", err) } fmt.Printf("%s\n", t2.Private) // OUTPUT: // foobar } ``` -------------------------------- ### JWT Parsing and Signing with Keys Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/Changes-v2.md Demonstrates basic JWT parsing with key verification enabled by default, and signing. Supports key sets and custom key providers. ```go // most basic jwt.Parse(serialized, jwt.WithKey(alg, key)) // NOTE: verification and validation are ENABLED by default! jwt.Sign(token, jwt.WithKey(alg,key)) ``` ```go // with a jwk.Set jwt.Parse(serialized, jwt.WithKeySet(set)) ``` ```go // UseDefault/InferAlgorithm with JWKS jwt.Parse(serialized, jwt.WithKeySet(set, jws.WithUseDefault(true), jws.WithInferAlgorithm(true)) ``` ```go // Use `jku` jwt.Parse(serialized, jwt.WithVerifyAuto(...)) ``` ```go // Any other custom key provisioning (using functions in this // example, but can be anything that fulfills jws.KeyProvider) jwt.Parse(serialized, jwt.WithKeyProvider(jws.KeyProviderFunc(...))) ``` -------------------------------- ### Registering Custom X509 PEM Decoder Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/Changes-v4.md Use jwkb.RegisterX509Decoder to install a custom PEM block decoder globally. The interface and its function adapter are now generic. ```go jwkbb.RegisterX509Decoder[T](blockType string, d X509Decoder[T]) error ``` -------------------------------- ### Get Destination Path for a Template Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/agents/docs/companions.md Uses the rendering script with the --dest flag to retrieve the calculated destination path for a given template and module. ```bash # Get destination path for a template python3 scripts/companion-render-template.py --dest .companions/templates/lint.yml ed448 ``` -------------------------------- ### Callback-based Option with Config Object Source: https://github.com/lestrrat-go/jwx/blob/develop/v4/docs/99-faq.md Demonstrates a callback-based option pattern using a separate configuration object to localize option effects. This pattern requires a state/config object for each method call that accepts options. ```go func (obj *Object) Method(options ...Options) error { var cfg MethodConfig for _, option := range options { option(obj, cfg) } ... } func MyOption(obj *Object, cfg *MethodConfig) { cfg.FieldA = ... cfg.FieldB = ... } ```