Repository: lestrrat-go/jwx
Pull request #716 (2022-04-24 04:14:16)

merge develop/v2


Patch:
diff --git a/Changes b/Changes
index d39deb0c..15800b01 100644
--- a/Changes
+++ b/Changes
@@ -4,6 +4,21 @@ Changes
 v2 has many incompatibilities with v1. To see the full list of differences between
 v1 and v2, please read the Changes-v2.md file (https://github.com/lestrrat-go/jwx/blob/develop/v2/Changes-v2.md)
 
+v2.0.0 - 24 Apr 2022
+  * This i the first v2 release, which represents a set of design changes
+    that were learnt over the previous 2 years. As a result the v2 API
+    should be much more consistent and uniform across packages, and
+    should be much more flexible to accomodate real-world needs.
+    
+    For a complete list of changes, please see the Changes-v2.md file,
+    or check the diff at https://github.com/lestrrat-go/jwx/compare/v1...v2
+
+[Miscellaneous]
+  * Minor house cleaning on code generation tools
+
+[jwt]
+  * `jwt.ErrMissingRequiredClaim()` has been added
+
 v2.0.0-beta2 - 16 Apr 2022
 [jwk]
   * Updated `jwk.Set` API and reflected pending changes from v1 which were
diff --git a/README.md b/README.md
index 07cf0f5f..537ae313 100644
--- a/README.md
+++ b/README.md
@@ -188,7 +188,7 @@ 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/JWS/JWK, which this library does.
+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
diff --git a/bench/performance/go.sum b/bench/performance/go.sum
index 14f9533d..c627099f 100644
--- a/bench/performance/go.sum
+++ b/bench/performance/go.sum
@@ -3,8 +3,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
-github.com/goccy/go-json v0.9.6 h1:5/4CtRQdtsX0sal8fdVhTaiMN01Ri8BExZZ8iRmHQ6E=
-github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
+github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
 github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
 github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
diff --git a/cmd/jwx/go.mod b/cmd/jwx/go.mod
index d4754518..18eac693 100644
--- a/cmd/jwx/go.mod
+++ b/cmd/jwx/go.mod
@@ -11,7 +11,7 @@ require (
 require (
 	github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
 	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
-	github.com/goccy/go-json v0.9.6 // indirect
+	github.com/goccy/go-json v0.9.7 // indirect
 	github.com/lestrrat-go/blackmagic v1.0.1 // indirect
 	github.com/lestrrat-go/httpcc v1.0.1 // indirect
 	github.com/lestrrat-go/httprc v1.0.1 // indirect
diff --git a/cmd/jwx/go.sum b/cmd/jwx/go.sum
index 49849fd7..7d8654fc 100644
--- a/cmd/jwx/go.sum
+++ b/cmd/jwx/go.sum
@@ -7,8 +7,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
-github.com/goccy/go-json v0.9.6 h1:5/4CtRQdtsX0sal8fdVhTaiMN01Ri8BExZZ8iRmHQ6E=
-github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
+github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
 github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
 github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
diff --git a/examples/go.sum b/examples/go.sum
index e0c4f5f4..ebbd452c 100644
--- a/examples/go.sum
+++ b/examples/go.sum
@@ -6,8 +6,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
-github.com/goccy/go-json v0.9.6 h1:5/4CtRQdtsX0sal8fdVhTaiMN01Ri8BExZZ8iRmHQ6E=
-github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
+github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
 github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
 github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
diff --git a/go.mod b/go.mod
index cc0a3b7e..13e06074 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.16
 
 require (
 	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1
-	github.com/goccy/go-json v0.9.6
+	github.com/goccy/go-json v0.9.7
 	github.com/lestrrat-go/blackmagic v1.0.1
 	github.com/lestrrat-go/httprc v1.0.1
 	github.com/lestrrat-go/iter v1.0.2
@@ -12,10 +12,3 @@ require (
 	github.com/stretchr/testify v1.7.1
 	golang.org/x/crypto v0.0.0-20220214200702-86341886e292
 )
-
-require (
-	github.com/davecgh/go-spew v1.1.0 // indirect
-	github.com/lestrrat-go/httpcc v1.0.1 // indirect
-	github.com/pmezard/go-difflib v1.0.0 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
-)
diff --git a/go.sum b/go.sum
index 14f9533d..c627099f 100644
--- a/go.sum
+++ b/go.sum
@@ -3,8 +3,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
-github.com/goccy/go-json v0.9.6 h1:5/4CtRQdtsX0sal8fdVhTaiMN01Ri8BExZZ8iRmHQ6E=
-github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
+github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
 github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
 github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
diff --git a/jwk/ecdsa_gen.go b/jwk/ecdsa_gen.go
index 3b83e091..92f56f5a 100644
--- a/jwk/ecdsa_gen.go
+++ b/jwk/ecdsa_gen.go
@@ -400,6 +400,8 @@ func (k *ecdsaPublicKey) SetDecodeCtx(dc json.DecodeCtx) {
 }
 
 func (h *ecdsaPublicKey) UnmarshalJSON(buf []byte) error {
+	h.mu.Lock()
+	defer h.mu.Unlock()
 	h.algorithm = nil
 	h.crv = nil
 	h.keyID = nil
@@ -982,6 +984,8 @@ func (k *ecdsaPrivateKey) SetDecodeCtx(dc json.DecodeCtx) {
 }
 
 func (h *ecdsaPrivateKey) UnmarshalJSON(buf []byte) error {
+	h.mu.Lock()
+	defer h.mu.Unlock()
 	h.algorithm = nil
 	h.crv = nil
 	h.d = nil
diff --git a/jwk/okp_gen.go b/jwk/okp_gen.go
index 8be02970..ccad6775 100644
--- a/jwk/okp_gen.go
+++ b/jwk/okp_gen.go
@@ -376,6 +376,8 @@ func (k *okpPublicKey) SetDecodeCtx(dc json.DecodeCtx) {
 }
 
 func (h *okpPublicKey) UnmarshalJSON(buf []byte) error {
+	h.mu.Lock()
+	defer h.mu.Unlock()
 	h.algorithm = nil
 	h.crv = nil
 	h.keyID = nil
@@ -928,6 +930,8 @@ func (k *okpPrivateKey) SetDecodeCtx(dc json.DecodeCtx) {
 }
 
 func (h *okpPrivateKey) UnmarshalJSON(buf []byte) error {
+	h.mu.Lock()
+	defer h.mu.Unlock()
 	h.algorithm = nil
 	h.crv = nil
 	h.d = nil
diff --git a/jwk/rsa_gen.go b/jwk/rsa_gen.go
index 60837290..ce4e400f 100644
--- a/jwk/rsa_gen.go
+++ b/jwk/rsa_gen.go
@@ -379,6 +379,8 @@ func (k *rsaPublicKey) SetDecodeCtx(dc json.DecodeCtx) {
 }
 
 func (h *rsaPublicKey) UnmarshalJSON(buf []byte) error {
+	h.mu.Lock()
+	defer h.mu.Unlock()
 	h.algorithm = nil
 	h.e = nil
 	h.keyID = nil
@@ -1036,6 +1038,8 @@ func (k *rsaPrivateKey) SetDecodeCtx(dc json.DecodeCtx) {
 }
 
 func (h *rsaPrivateKey) UnmarshalJSON(buf []byte) error {
+	h.mu.Lock()
+	defer h.mu.Unlock()
 	h.algorithm = nil
 	h.d = nil
 	h.dp = nil
diff --git a/jwk/symmetric_gen.go b/jwk/symmetric_gen.go
index 7be6d5f2..6a965193 100644
--- a/jwk/symmetric_gen.go
+++ b/jwk/symmetric_gen.go
@@ -349,6 +349,8 @@ func (k *symmetricKey) SetDecodeCtx(dc json.DecodeCtx) {
 }
 
 func (h *symmetricKey) UnmarshalJSON(buf []byte) error {
+	h.mu.Lock()
+	defer h.mu.Unlock()
 	h.algorithm = nil
 	h.keyID = nil
 	h.keyOps = nil
diff --git a/jws/headers_gen.go b/jws/headers_gen.go
index c3436b35..fd892e2c 100644
--- a/jws/headers_gen.go
+++ b/jws/headers_gen.go
@@ -175,6 +175,22 @@ func (h *stdHeaders) X509URL() string {
 	return *(h.x509URL)
 }
 
+func (h *stdHeaders) clear() {
+	h.algorithm = nil
+	h.contentType = nil
+	h.critical = nil
+	h.jwk = nil
+	h.jwkSetURL = nil
+	h.keyID = nil
+	h.typ = nil
+	h.x509CertChain = nil
+	h.x509CertThumbprint = nil
+	h.x509CertThumbprintS256 = nil
+	h.x509URL = nil
+	h.privateParams = nil
+	h.raw = nil
+}
+
 func (h *stdHeaders) DecodeCtx() DecodeCtx {
 	h.mu.RLock()
 	defer h.mu.RUnlock()
@@ -425,17 +441,9 @@ func (h *stdHeaders) Remove(key string) error {
 }
 
 func (h *stdHeaders) UnmarshalJSON(buf []byte) error {
-	h.algorithm = nil
-	h.contentType = nil
-	h.critical = nil
-	h.jwk = nil
-	h.jwkSetURL = nil
-	h.keyID = nil
-	h.typ = nil
-	h.x509CertChain = nil
-	h.x509CertThumbprint = nil
-	h.x509CertThumbprintS256 = nil
-	h.x509URL = nil
+	h.mu.Lock()
+	defer h.mu.Unlock()
+	h.clear()
 	dec := json.NewDecoder(bytes.NewReader(buf))
 LOOP:
 	for {
diff --git a/jwt/openid/token_gen.go b/jwt/openid/token_gen.go
index 607314a1..81c40055 100644
--- a/jwt/openid/token_gen.go
+++ b/jwt/openid/token_gen.go
@@ -1161,8 +1161,6 @@ LOOP:
 }
 
 func (t stdToken) MarshalJSON() ([]byte, error) {
-	t.mu.RLock()
-	defer t.mu.RUnlock()
 	buf := pool.GetBytesBuffer()
 	defer pool.ReleaseBytesBuffer(buf)
 	buf.WriteByte('{')
diff --git a/jwt/token_gen.go b/jwt/token_gen.go
index 03c7caa5..4a09a3b6 100644
--- a/jwt/token_gen.go
+++ b/jwt/token_gen.go
@@ -462,8 +462,6 @@ LOOP:
 }
 
 func (t stdToken) MarshalJSON() ([]byte, error) {
-	t.mu.RLock()
-	defer t.mu.RUnlock()
 	buf := pool.GetBytesBuffer()
 	defer pool.ReleaseBytesBuffer(buf)
 	buf.WriteByte('{')
diff --git a/jwt/validate.go b/jwt/validate.go
index d8148d5e..f4a4aa67 100644
--- a/jwt/validate.go
+++ b/jwt/validate.go
@@ -159,6 +159,22 @@ func (err *validationError) Unwrap() error {
 	return err.error
 }
 
+type missingRequiredClaimError struct {
+	claim string
+}
+
+func (err *missingRequiredClaimError) Error() string {
+	return fmt.Sprintf("%q not satisfied: required claim not found", err.claim)
+}
+
+func (err *missingRequiredClaimError) Is(target error) bool {
+	_, ok := target.(*missingRequiredClaimError)
+	return ok
+}
+
+func (err *missingRequiredClaimError) isValidationError() {}
+func (*missingRequiredClaimError) Unwrap() error          { return nil }
+
 var errTokenExpired = NewValidationError(fmt.Errorf(`"exp" not satisfied`))
 var errInvalidIssuedAt = NewValidationError(fmt.Errorf(`"iat" not satisfied`))
 var errTokenNotYetValid = NewValidationError(fmt.Errorf(`"nbf" not satisfied`))
@@ -179,6 +195,11 @@ func ErrTokenNotYetValid() ValidationError {
 	return errTokenNotYetValid
 }
 
+// ErrMissingRequiredClaim creates a new error for missing required claims.
+func ErrMissingRequiredClaim(name string) ValidationError {
+	return &missingRequiredClaimError{claim: name}
+}
+
 // Validator describes interface to validate a Token.
 type Validator interface {
 	// Validate should return an error if a required conditions is not met.
@@ -377,9 +398,10 @@ func IsRequired(name string) Validator {
 type isRequired string
 
 func (ir isRequired) Validate(_ context.Context, t Token) ValidationError {
-	_, ok := t.Get(string(ir))
+	name := string(ir)
+	_, ok := t.Get(name)
 	if !ok {
-		return NewValidationError(fmt.Errorf(`%q not satisfied: required claim not found`, string(ir)))
+		return ErrMissingRequiredClaim(name)
 	}
 	return nil
 }
diff --git a/tools/cmd/genjwe/go.mod b/tools/cmd/genjwe/go.mod
index 5b1226bd..7ee8345d 100644
--- a/tools/cmd/genjwe/go.mod
+++ b/tools/cmd/genjwe/go.mod
@@ -4,7 +4,7 @@ go 1.16
 
 require (
 	github.com/goccy/go-yaml v1.9.4
-	github.com/lestrrat-go/codegen v1.0.4-0.20220329032654-7b542c9fb49b
+	github.com/lestrrat-go/codegen v1.0.4-0.20220422093832-3f381ea644e3
 	github.com/stretchr/testify v1.7.1 // indirect
 	golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
 )
diff --git a/tools/cmd/genjwe/go.sum b/tools/cmd/genjwe/go.sum
index 952c1ee2..026adac4 100644
--- a/tools/cmd/genjwe/go.sum
+++ b/tools/cmd/genjwe/go.sum
@@ -13,8 +13,8 @@ github.com/goccy/go-yaml v1.9.4 h1:S0GCYjwHKVI6IHqio7QWNKNThUl6NLzFd/g8Z65Axw8=
 github.com/goccy/go-yaml v1.9.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
-github.com/lestrrat-go/codegen v1.0.4-0.20220329032654-7b542c9fb49b h1:PQ+SVFMytiSbfozcIQbp4Kb7/xe6NPiHdFNVQxnynlk=
-github.com/lestrrat-go/codegen v1.0.4-0.20220329032654-7b542c9fb49b/go.mod h1:B5zvyPBGsMtVrUaM9q0doiP+oyw05CI/dpFU9T1TUFo=
+github.com/lestrrat-go/codegen v1.0.4-0.20220422093832-3f381ea644e3 h1:ePhghrkFCIQH5Wnq5R5H5iw+RXdYkc53M/bcc7ylqpQ=
+github.com/lestrrat-go/codegen v1.0.4-0.20220422093832-3f381ea644e3/go.mod h1:B5zvyPBGsMtVrUaM9q0doiP+oyw05CI/dpFU9T1TUFo=
 github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
 github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
 github.com/lestrrat-go/xstrings v0.0.0-20210804220435-4dd8b234342b h1:3laG8JWIeDGb7lf00nMRznLdCHy0aZPd/CGz7Okn1SY=
diff --git a/tools/cmd/genjwe/main.go b/tools/cmd/genjwe/main.go
index 9f367758..054209eb 100644
--- a/tools/cmd/genjwe/main.go
+++ b/tools/cmd/genjwe/main.go
@@ -55,24 +55,6 @@ func yaml2json(fn string) ([]byte, error) {
 	return json.Marshal(v)
 }
 
-func boolFromField(f codegen.Field, field string) (bool, error) {
-	v, ok := f.Extra(field)
-	if !ok {
-		return false, fmt.Errorf("%q does not exist in %q", field, f.Name(true))
-	}
-
-	b, ok := v.(bool)
-	if !ok {
-		return false, fmt.Errorf("%q should be a bool in %q", field, f.Name(true))
-	}
-	return b, nil
-}
-
-func fieldHasAccept(f codegen.Field) bool {
-	v, _ := boolFromField(f, "hasAccept")
-	return v
-}
-
 func IsPointer(f codegen.Field) bool {
 	return strings.HasPrefix(f.Type(), `*`)
 }
@@ -234,7 +216,7 @@ func generateHeaders(obj *codegen.Object) error {
 	o.L("switch name {")
 	for _, f := range obj.Fields() {
 		o.L("case %sKey:", f.Name(true))
-		if fieldHasAccept(f) {
+		if f.Bool(`hasAccept`) {
 			o.L("var acceptor %s", PointerElem(f))
 			o.L("if err := acceptor.Accept(value); err != nil {")
 			o.L("return fmt.Errorf(`invalid value for %%s key: %%w`, %sKey, err)", f.Name(true))
diff --git a/tools/cmd/genjwk/go.mod b/tools/cmd/genjwk/go.mod
index 79da8af7..510ec12b 100644
--- a/tools/cmd/genjwk/go.mod
+++ b/tools/cmd/genjwk/go.mod
@@ -4,7 +4,7 @@ go 1.16
 
 require (
 	github.com/goccy/go-yaml v1.9.4
-	github.com/lestrrat-go/codegen v1.0.4-0.20220329032654-7b542c9fb49b
+	github.com/lestrrat-go/codegen v1.0.4-0.20220422093832-3f381ea644e3
 	github.com/stretchr/testify v1.7.1 // indirect
 	golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
 )
diff --git a/tools/cmd/genjwk/go.sum b/tools/cmd/genjwk/go.sum
index 952c1ee2..026adac4 100644
--- a/tools/cmd/genjwk/go.sum
+++ b/tools/cmd/genjwk/go.sum
@@ -13,8 +13,8 @@ github.com/goccy/go-yaml v1.9.4 h1:S0GCYjwHKVI6IHqio7QWNKNThUl6NLzFd/g8Z65Axw8=
 github.com/goccy/go-yaml v1.9.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
-github.com/lestrrat-go/codegen v1.0.4-0.20220329032654-7b542c9fb49b h1:PQ+SVFMytiSbfozcIQbp4Kb7/xe6NPiHdFNVQxnynlk=
-github.com/lestrrat-go/codegen v1.0.4-0.20220329032654-7b542c9fb49b/go.mod h1:B5zvyPBGsMtVrUaM9q0doiP+oyw05CI/dpFU9T1TUFo=
+github.com/lestrrat-go/codegen v1.0.4-0.20220422093832-3f381ea644e3 h1:ePhghrkFCIQH5Wnq5R5H5iw+RXdYkc53M/bcc7ylqpQ=
+github.com/lestrrat-go/codegen v1.0.4-0.20220422093832-3f381ea644e3/go.mod h1:B5zvyPBGsMtVrUaM9q0doiP+oyw05CI/dpFU9T1TUFo=
 github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
 github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
 github.com/lestrrat-go/xstrings v0.0.0-20210804220435-4dd8b234342b h1:3laG8JWIeDGb7lf00nMRznLdCHy0aZPd/CGz7Okn1SY=
diff --git a/tools/cmd/genjwk/main.go b/tools/cmd/genjwk/main.go
index 5790be04..d0d4b77a 100644
--- a/tools/cmd/genjwk/main.go
+++ b/tools/cmd/genjwk/main.go
@@ -92,60 +92,6 @@ func _main() error {
 	return nil
 }
 
-func boolFromField(f codegen.Field, field string) (bool, error) {
-	v, ok := f.Extra(field)
-	if !ok {
-		return false, fmt.Errorf("%q does not exist in %q", field, f.Name(true))
-	}
-
-	b, ok := v.(bool)
-	if !ok {
-		return false, fmt.Errorf("%q should be a bool in %q", field, f.Name(true))
-	}
-	return b, nil
-}
-
-func stringFromField(f codegen.Field, field string) (string, error) {
-	v, ok := f.Extra(field)
-	if !ok {
-		return "", fmt.Errorf("%q does not exist in %q", field, f.Name(true))
-	}
-
-	s, ok := v.(string)
-	if !ok {
-		return "", fmt.Errorf("%q should be a string in %q", field, f.Name(true))
-	}
-	return s, nil
-}
-
-func fieldIsStd(f codegen.Field) bool {
-	v, _ := boolFromField(f, `is_std`)
-	return v
-}
-
-func fieldNoDeref(f codegen.Field) bool {
-	v, _ := boolFromField(f, "noDeref")
-	return v
-}
-
-func fieldHasGet(f codegen.Field) bool {
-	v, _ := boolFromField(f, "hasGet")
-	return v
-}
-
-func fieldHasAccept(f codegen.Field) bool {
-	v, _ := boolFromField(f, "hasAccept")
-	return v
-}
-
-func fieldGetterReturnValue(f codegen.Field) string {
-	if v, err := stringFromField(f, `getter_return_value`); err == nil {
-		return v
-	}
-
-	return f.Type()
-}
-
 func IsPointer(f codegen.Field) bool {
 	return strings.HasPrefix(f.Type(), `*`)
 }
@@ -164,43 +110,6 @@ func fieldStorageTypeIsIndirect(s string) bool {
 	return s == "KeyOperationList" || !(strings.HasPrefix(s, `*`) || strings.HasPrefix(s, `[]`) || strings.HasSuffix(s, `List`))
 }
 
-func stringFromObject(o *codegen.Object, field string) (string, error) {
-	v, ok := o.Extra(field)
-	if !ok {
-		return "", fmt.Errorf("%q does not exist in %q", field, o.Name(true))
-	}
-
-	s, ok := v.(string)
-	if !ok {
-		return "", fmt.Errorf("%q should be a string in %q", field, o.Name(true))
-	}
-	return s, nil
-}
-
-func objectRawKeyType(obj *codegen.Object) string {
-	v, err := stringFromObject(obj, "raw_key_type")
-	if err != nil {
-		panic(err.Error())
-	}
-	return v
-}
-
-func objectStructName(o *codegen.Object) string {
-	v, err := stringFromObject(o, `struct_name`)
-	if err != nil {
-		return ""
-	}
-	return v
-}
-
-func objectInterface(o *codegen.Object) string {
-	v, err := stringFromObject(o, `interface`)
-	if err != nil {
-		return ""
-	}
-	return v
-}
-
 type Constant struct {
 	Name  string
 	Value string
@@ -217,7 +126,7 @@ func generateKeyType(kt *KeyType) error {
 	seen := make(map[string]struct{})
 	for _, obj := range kt.Objects {
 		for _, f := range obj.Fields() {
-			if fieldIsStd(f) {
+			if f.Bool(`is_std`) {
 				continue
 			}
 			n := f.Name(true)
@@ -241,7 +150,7 @@ func generateKeyType(kt *KeyType) error {
 
 	for _, obj := range kt.Objects {
 		if err := generateObject(o, kt, obj); err != nil {
-			return fmt.Errorf(`failed to generate object %s: %w`, obj.Name(true))
+			return fmt.Errorf(`failed to generate object %s: %w`, obj.Name(true), err)
 		}
 	}
 
@@ -256,19 +165,19 @@ func generateKeyType(kt *KeyType) error {
 
 func generateObject(o *codegen.Output, kt *KeyType, obj *codegen.Object) error {
 	ifName := kt.Prefix + obj.Name(true)
-	if v := objectInterface(obj); v != "" {
+	if v := obj.String(`interface`); v != "" {
 		ifName = v
 	}
 	structName := strings.ToLower(kt.Prefix) + obj.Name(true)
-	if v := objectStructName(obj); v != "" {
+	if v := obj.String(`struct_name`); v != "" {
 		structName = v
 	}
 
 	o.LL("type %s interface {", ifName)
 	o.L("Key")
-	o.L("FromRaw(%s) error", objectRawKeyType(obj))
+	o.L("FromRaw(%s) error", obj.MustString(`raw_key_type`))
 	for _, f := range obj.Fields() {
-		if fieldIsStd(f) {
+		if f.Bool(`is_std`) {
 			continue
 		}
 		o.L("%s() %s", f.GetterMethod(true), f.Type())
@@ -303,16 +212,16 @@ func generateObject(o *codegen.Output, kt *KeyType, obj *codegen.Object) error {
 
 	for _, f := range obj.Fields() {
 		o.LL("func (h *%s) %s() ", structName, f.GetterMethod(true))
-		if v := fieldGetterReturnValue(f); v != "" {
+		if v := f.String(`getter_return_value`); v != "" {
 			o.R("%s", v)
-		} else if IsPointer(f) && fieldNoDeref(f) {
+		} else if IsPointer(f) && f.Bool(`noDeref`) {
 			o.R("%s", f.Type())
 		} else {
 			o.R("%s", PointerElem(f))
 		}
 		o.R(" {")
 
-		if fieldHasGet(f) {
+		if f.Bool(`hasGet`) {
 			o.L("if h.%s != nil {", f.Name(false))
 			o.L("return h.%s.Get()", f.Name(false))
 			o.L("}")
@@ -341,7 +250,7 @@ func generateObject(o *codegen.Output, kt *KeyType, obj *codegen.Object) error {
 	o.L("pairs = append(pairs, &HeaderPair{Key: \"kty\", Value: %s})", kt.KeyType)
 	for _, f := range obj.Fields() {
 		var keyName string
-		if fieldIsStd(f) {
+		if f.Bool(`is_std`) {
 			keyName = f.Name(true) + "Key"
 		} else {
 			keyName = kt.Prefix + f.Name(true) + "Key"
@@ -371,7 +280,7 @@ func generateObject(o *codegen.Output, kt *KeyType, obj *codegen.Object) error {
 	o.L("case KeyTypeKey:")
 	o.L("return h.KeyType(), true")
 	for _, f := range obj.Fields() {
-		if fieldIsStd(f) {
+		if f.Bool(`is_std`) {
 			o.L("case %sKey:", f.Name(true))
 		} else {
 			o.L("case %s%sKey:", kt.Prefix, f.Name(true))
@@ -380,7 +289,7 @@ func generateObject(o *codegen.Output, kt *KeyType, obj *codegen.Object) error {
 		o.L("if h.%s == nil {", f.Name(false))
 		o.L("return nil, false")
 		o.L("}")
-		if fieldHasGet(f) {
+		if f.Bool(`hasGet`) {
 			o.L("return h.%s.Get(), true", f.Name(false))
 		} else if fieldStorageTypeIsIndirect(f.Type()) {
 			o.L("return *(h.%s), true", f.Name(false))
@@ -406,7 +315,7 @@ func generateObject(o *codegen.Output, kt *KeyType, obj *codegen.Object) error {
 	o.L("return nil") // This is not great, but we just ignore it
 	for _, f := range obj.Fields() {
 		var keyName string
-		if fieldIsStd(f) {
+		if f.Bool(`is_std`) {
 			keyName = f.Name(true) + "Key"
 		} else {
 			keyName = kt.Prefix + f.Name(true) + "Key"
@@ -440,7 +349,7 @@ func generateObject(o *codegen.Output, kt *KeyType, obj *codegen.Object) error {
 			o.L("default:")
 			o.L("return fmt.Errorf(`invalid key usage type %%s`, v)")
 			o.L("}")
-		} else if fieldHasAccept(f) {
+		} else if f.Bool(`hasAccept`) {
 			o.L("var acceptor %s", f.Type())
 			o.L("if err := acceptor.Accept(value); err != nil {")
 			o.L("return fmt.Errorf(`invalid value for %%s key: %%w`, %s, err)", keyName)
@@ -478,7 +387,7 @@ func generateObject(o *codegen.Output, kt *KeyType, obj *codegen.Object) error {
 	o.L("switch key {")
 	for _, f := range obj.Fields() {
 		var keyName string
-		if fieldIsStd(f) {
+		if f.Bool(`is_std`) {
 			keyName = f.Name(true) + "Key"
 		} else {
 			keyName = kt.Prefix + f.Name(true) + "Key"
@@ -509,6 +418,8 @@ func generateObject(o *codegen.Output, kt *KeyType, obj *codegen.Object) error {
 	o.L("}")
 
 	o.LL("func (h *%s) UnmarshalJSON(buf []byte) error {", structName)
+	o.L(`h.mu.Lock()`)
+	o.L(`defer h.mu.Unlock()`)
 	for _, f := range obj.Fields() {
 		o.L("h.%s = nil", f.Name(false))
 	}
@@ -769,9 +680,9 @@ func generateGenericHeaders(fields codegen.FieldList) error {
 			o.L("// This is why there exists a `jwa.KeyAlgorithm` type that encompases both types.")
 		}
 		o.L("%s() ", f.GetterMethod(true))
-		if v := fieldGetterReturnValue(f); v != "" {
+		if v := f.String(`getter_return_value`); v != "" {
 			o.R("%s", v)
-		} else if IsPointer(f) && fieldNoDeref(f) {
+		} else if IsPointer(f) && f.Bool(`noDeref`) {
 			o.R("%s", f.Type())
 		} else {
 			o.R("%s", PointerElem(f))
diff --git a/tools/cmd/genjws/go.mod b/tools/cmd/genjws/go.mod
index 71e3fd50..3464df01 100644
--- a/tools/cmd/genjws/go.mod
+++ b/tools/cmd/genjws/go.mod
@@ -4,7 +4,7 @@ go 1.16
 
 require (
 	github.com/goccy/go-yaml v1.9.4
-	github.com/lestrrat-go/codegen v1.0.4-0.20220329032654-7b542c9fb49b
+	github.com/lestrrat-go/codegen v1.0.4-0.20220422093832-3f381ea644e3
 	github.com/stretchr/testify v1.7.1 // indirect
 	golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
 )
diff --git a/tools/cmd/genjws/go.sum b/tools/cmd/genjws/go.sum
index 952c1ee2..026adac4 100644
--- a/tools/cmd/genjws/go.sum
+++ b/tools/cmd/genjws/go.sum
@@ -13,8 +13,8 @@ github.com/goccy/go-yaml v1.9.4 h1:S0GCYjwHKVI6IHqio7QWNKNThUl6NLzFd/g8Z65Axw8=
 github.com/goccy/go-yaml v1.9.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
-github.com/lestrrat-go/codegen v1.0.4-0.20220329032654-7b542c9fb49b h1:PQ+SVFMytiSbfozcIQbp4Kb7/xe6NPiHdFNVQxnynlk=
-github.com/lestrrat-go/codegen v1.0.4-0.20220329032654-7b542c9fb49b/go.mod h1:B5zvyPBGsMtVrUaM9q0doiP+oyw05CI/dpFU9T1TUFo=
+github.com/lestrrat-go/codegen v1.0.4-0.20220422093832-3f381ea644e3 h1:ePhghrkFCIQH5Wnq5R5H5iw+RXdYkc53M/bcc7ylqpQ=
+github.com/lestrrat-go/codegen v1.0.4-0.20220422093832-3f381ea644e3/go.mod h1:B5zvyPBGsMtVrUaM9q0doiP+oyw05CI/dpFU9T1TUFo=
 github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
 github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
 github.com/lestrrat-go/xstrings v0.0.0-20210804220435-4dd8b234342b h1:3laG8JWIeDGb7lf00nMRznLdCHy0aZPd/CGz7Okn1SY=
diff --git a/tools/cmd/genjws/main.go b/tools/cmd/genjws/main.go
index 049beb28..bf652113 100644
--- a/tools/cmd/genjws/main.go
+++ b/tools/cmd/genjws/main.go
@@ -54,29 +54,6 @@ func yaml2json(fn string) ([]byte, error) {
 	return json.Marshal(v)
 }
 
-func boolFromField(f codegen.Field, field string) (bool, error) {
-	v, ok := f.Extra(field)
-	if !ok {
-		return false, fmt.Errorf("%q does not exist in %q", field, f.Name(true))
-	}
-
-	b, ok := v.(bool)
-	if !ok {
-		return false, fmt.Errorf("%q should be a bool in %q", field, f.Name(true))
-	}
-	return b, nil
-}
-
-func fieldNoDeref(f codegen.Field) bool {
-	v, _ := boolFromField(f, "noDeref")
-	return v
-}
-
-func fieldHasAccept(f codegen.Field) bool {
-	v, _ := boolFromField(f, "hasAccept")
-	return v
-}
-
 func IsPointer(f codegen.Field) bool {
 	return strings.HasPrefix(f.Type(), `*`)
 }
@@ -115,7 +92,7 @@ func generateHeaders(obj *codegen.Object) error {
 	o.L("json.Unmarshaler")
 	// These are the basic values that most jws have
 	for _, f := range obj.Fields() {
-		if fieldNoDeref(f) {
+		if f.Bool(`noDeref`) {
 			o.L("%s() %s", f.GetterMethod(true), f.Type())
 		} else {
 			o.L("%s() %s", f.GetterMethod(true), PointerElem(f))
@@ -176,6 +153,14 @@ func generateHeaders(obj *codegen.Object) error {
 		o.L("}") // func (h *stdHeaders) %s() %s
 	}
 
+	o.LL("func (h *stdHeaders) clear() {")
+	for _, f := range obj.Fields() {
+		o.L("h.%s = nil", f.Name(false))
+	}
+	o.L("h.privateParams = nil")
+	o.L("h.raw = nil")
+	o.L("}")
+
 	o.LL("func (h *stdHeaders) DecodeCtx() DecodeCtx{")
 	o.L("h.mu.RLock()")
 	o.L("defer h.mu.RUnlock()")
@@ -254,7 +239,7 @@ func generateHeaders(obj *codegen.Object) error {
 	o.L("switch name {")
 	for _, f := range obj.Fields() {
 		o.L("case %sKey:", f.Name(true))
-		if fieldHasAccept(f) {
+		if f.Bool(`hasAccept`) {
 			o.L("var acceptor %s", PointerElem(f))
 			o.L("if err := acceptor.Accept(value); err != nil {")
 			o.L("return fmt.Errorf(`invalid value for %%s key: %%w`, %sKey, err)", f.Name(true))
@@ -297,10 +282,9 @@ func generateHeaders(obj *codegen.Object) error {
 	o.L("}")
 
 	o.LL("func (h *stdHeaders) UnmarshalJSON(buf []byte) error {")
-	for _, f := range obj.Fields() {
-		o.L("h.%s = nil", f.Name(false))
-	}
-
+	o.L("h.mu.Lock()")
+	o.L("defer h.mu.Unlock()")
+	o.L("h.clear()")
 	o.L("dec := json.NewDecoder(bytes.NewReader(buf))")
 	o.L("LOOP:")
 	o.L("for {")
@@ -349,7 +333,7 @@ func generateHeaders(obj *codegen.Object) error {
 			o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true))
 			o.L("}")
 			o.L("h.%s = decoded", f.Name(false))
-		} else if fieldNoDeref(f) {
+		} else if f.Bool(`noDeref`) {
 			o.L("case %sKey:", f.Name(true))
 			o.L("var decoded %s", PointerElem(f))
 			o.L("if err := dec.Decode(&decoded); err != nil {")
diff --git a/tools/cmd/genjwt/go.mod b/tools/cmd/genjwt/go.mod
index 2cf74ff1..a509e63f 100644
--- a/tools/cmd/genjwt/go.mod
+++ b/tools/cmd/genjwt/go.mod
@@ -5,7 +5,7 @@ go 1.16
 require (
 	github.com/goccy/go-json v0.9.6
 	github.com/goccy/go-yaml v1.9.4
-	github.com/lestrrat-go/codegen v1.0.4-0.20220329032654-7b542c9fb49b
+	github.com/lestrrat-go/codegen v1.0.4-0.20220422093832-3f381ea644e3
 	github.com/stretchr/testify v1.7.1 // indirect
 	golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
 )
diff --git a/tools/cmd/genjwt/go.sum b/tools/cmd/genjwt/go.sum
index 81720884..2661b17e 100644
--- a/tools/cmd/genjwt/go.sum
+++ b/tools/cmd/genjwt/go.sum
@@ -15,8 +15,8 @@ github.com/goccy/go-yaml v1.9.4 h1:S0GCYjwHKVI6IHqio7QWNKNThUl6NLzFd/g8Z65Axw8=
 github.com/goccy/go-yaml v1.9.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
-github.com/lestrrat-go/codegen v1.0.4-0.20220329032654-7b542c9fb49b h1:PQ+SVFMytiSbfozcIQbp4Kb7/xe6NPiHdFNVQxnynlk=
-github.com/lestrrat-go/codegen v1.0.4-0.20220329032654-7b542c9fb49b/go.mod h1:B5zvyPBGsMtVrUaM9q0doiP+oyw05CI/dpFU9T1TUFo=
+github.com/lestrrat-go/codegen v1.0.4-0.20220422093832-3f381ea644e3 h1:ePhghrkFCIQH5Wnq5R5H5iw+RXdYkc53M/bcc7ylqpQ=
+github.com/lestrrat-go/codegen v1.0.4-0.20220422093832-3f381ea644e3/go.mod h1:B5zvyPBGsMtVrUaM9q0doiP+oyw05CI/dpFU9T1TUFo=
 github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
 github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
 github.com/lestrrat-go/xstrings v0.0.0-20210804220435-4dd8b234342b h1:3laG8JWIeDGb7lf00nMRznLdCHy0aZPd/CGz7Okn1SY=
diff --git a/tools/cmd/genjwt/main.go b/tools/cmd/genjwt/main.go
index b1dbafe3..4c52fe3c 100644
--- a/tools/cmd/genjwt/main.go
+++ b/tools/cmd/genjwt/main.go
@@ -51,105 +51,19 @@ func _main() error {
 
 	for _, object := range def.Objects {
 		if err := generateToken(object); err != nil {
-			return fmt.Errorf(`failed to generate token file %s: %w`, objectFilename(object), err)
+			return fmt.Errorf(`failed to generate token file %s: %w`, object.MustString(`filename`), err)
 		}
 	}
 
 	for _, object := range def.Objects {
 		if err := genBuilder(object); err != nil {
-			return fmt.Errorf(`failed to generate builder for package %q: %w`, objectPackage(object), err)
+			return fmt.Errorf(`failed to generate builder for package %q: %w`, object.MustString(`package`), err)
 		}
 	}
 
 	return nil
 }
 
-func boolFromField(f codegen.Field, field string) (bool, error) {
-	v, ok := f.Extra(field)
-	if !ok {
-		return false, fmt.Errorf("%q does not exist in %q", field, f.Name(true))
-	}
-
-	b, ok := v.(bool)
-	if !ok {
-		return false, fmt.Errorf("%q should be a bool in %q", field, f.Name(true))
-	}
-	return b, nil
-}
-
-func stringFromField(f codegen.Field, field string) (string, error) {
-	v, ok := f.Extra(field)
-	if !ok {
-		return "", fmt.Errorf("%q does not exist in %q", field, f.Name(true))
-	}
-
-	s, ok := v.(string)
-	if !ok {
-		return "", fmt.Errorf("%q should be a string in %q", field, f.Name(true))
-	}
-	return s, nil
-}
-
-func fieldNoDeref(f codegen.Field) bool {
-	v, _ := boolFromField(f, "noDeref")
-	return v
-}
-
-func fieldHasGet(f codegen.Field) bool {
-	v, _ := boolFromField(f, "hasGet")
-	return v
-}
-
-func fieldHasAccept(f codegen.Field) bool {
-	v, _ := boolFromField(f, "hasAccept")
-	return v
-}
-
-func fieldGetterReturnValue(f codegen.Field) string {
-	if v, err := stringFromField(f, `getter_return_value`); err == nil {
-		return v
-	}
-
-	return f.Type()
-}
-
-func stringFromObject(o *codegen.Object, field string) (string, error) {
-	v, ok := o.Extra(field)
-	if !ok {
-		return "", fmt.Errorf("%q does not exist in %q", field, o.Name(true))
-	}
-
-	s, ok := v.(string)
-	if !ok {
-		return "", fmt.Errorf("%q should be a string in %q", field, o.Name(true))
-	}
-	return s, nil
-}
-
-func objectFilename(o *codegen.Object) string {
-	v, err := stringFromObject(o, `filename`)
-	if err != nil {
-		panic(err.Error())
-	}
-	return v
-}
-
-func objectPackage(o *codegen.Object) string {
-	v, err := stringFromObject(o, `package`)
-	if err != nil {
-		panic(err.Error())
-	}
-	return v
-}
-
-func objectInterface(o *codegen.Object) string {
-	v, err := stringFromObject(o, `interface`)
-	if err != nil {
-		panic(err.Error())
-	}
-	return v
-}
-
 func yaml2json(fn string) ([]byte, error) {
 	in, err := os.Open(fn)
 	if err != nil {
@@ -189,7 +103,7 @@ func generateToken(obj *codegen.Object) error {
 
 	o := codegen.NewOutput(&buf)
 	o.L("// This file is auto-generated by jwt/internal/cmd/gentoken/main.go. DO NOT EDIT")
-	o.LL("package %s", objectPackage(obj))
+	o.LL("package %s", obj.String(`package`))
 
 	var fields = obj.Fields()
 
@@ -199,7 +113,7 @@ func generateToken(obj *codegen.Object) error {
 	}
 	o.L(")") // end const
 
-	if objectPackage(obj) == "jwt" && obj.Name(false) == "stdToken" {
+	if obj.String(`package`) == "jwt" && obj.Name(false) == "stdToken" {
 		o.LL("// Token represents a generic JWT token.")
 		o.L("// which are type-aware (to an extent). Other claims may be accessed via the `Get`/`Set`")
 		o.L("// methods but their types are not taken into consideration at all. If you have non-standard")
@@ -212,10 +126,15 @@ func generateToken(obj *codegen.Object) error {
 		o.L("// work well when it is embedded in other structure")
 	}
 
-	o.L("type %s interface {", objectInterface(obj))
+	o.L("type %s interface {", obj.String(`interface`))
 	for _, field := range fields {
 		o.LL("// %s returns the value for %q field of the token", field.GetterMethod(true), field.JSON())
-		o.L("%s() %s", field.GetterMethod(true), fieldGetterReturnValue(field))
+
+		rv := field.String(`getter_return_value`)
+		if rv == "" {
+			rv = field.Type()
+		}
+		o.L("%s() %s", field.GetterMethod(true), rv)
 	}
 	o.LL("// PrivateClaims return the entire set of fields (claims) in the token")
 	o.L("// *other* than the pre-defined fields such as `iss`, `nbf`, `iat`, etc.")
@@ -236,7 +155,7 @@ func generateToken(obj *codegen.Object) error {
 	o.L("// for the types of each of these fields")
 	o.L("Set(string, interface{}) error")
 	o.L("Remove(string) error")
-	if objectPackage(obj) != "jwt" {
+	if obj.String(`package`) != "jwt" {
 		o.L("Clone() (jwt.Token, error)")
 	} else {
 		o.L("Clone() (Token, error)")
@@ -272,7 +191,7 @@ func generateToken(obj *codegen.Object) error {
 	}
 
 	o.R(".\n// Convenience accessors are provided for these standard claims")
-	o.L("func New() %s {", objectInterface(obj))
+	o.L("func New() %s {", obj.String(`interface`))
 	o.L("return &%s{", obj.Name(false))
 	o.L("mu: &sync.RWMutex{},")
 	o.L("privateClaims: make(map[string]interface{}),")
@@ -288,7 +207,7 @@ func generateToken(obj *codegen.Object) error {
 		o.L("if t.%s == nil {", f.Name(false))
 		o.L("return nil, false")
 		o.L("}")
-		if fieldHasGet(f) {
+		if f.Bool(`hasGet`) {
 			o.L("v := t.%s.Get()", f.Name(false))
 		} else {
 			if fieldStorageTypeIsIndirect(f.Type()) {
@@ -353,7 +272,7 @@ func generateToken(obj *codegen.Object) error {
 			o.L("return fmt.Errorf(`invalid type for %%s key: %%T`, %s, value)", keyName)
 			o.L("}")
 			o.L("return nil")
-		} else if fieldHasAccept(f) {
+		} else if f.Bool(`hasAccept`) {
 			if IsPointer(f) {
 				o.L("var acceptor %s", strings.TrimPrefix(f.Type(), "*"))
 			} else {
@@ -391,10 +310,14 @@ func generateToken(obj *codegen.Object) error {
 	o.L("}") // end func (t *%s) Set(name string, value interface{})
 
 	for _, f := range fields {
+		rv := f.String(`getter_return_value`)
+		if rv == "" {
+			rv = f.Type()
+		}
 		o.LL("func (t *%s) %s() ", obj.Name(false), f.GetterMethod(true))
-		if rv := fieldGetterReturnValue(f); rv != "" {
+		if rv != "" {
 			o.R("%s", rv)
-		} else if IsPointer(f) && fieldNoDeref(f) {
+		} else if IsPointer(f) && f.Bool(`noDeref`) {
 			o.R("%s", f.Type())
 		} else {
 			o.R("%s", PointerElem(f))
@@ -403,17 +326,17 @@ func generateToken(obj *codegen.Object) error {
 		o.L("t.mu.RLock()")
 		o.L("defer t.mu.RUnlock()")
 
-		if fieldHasGet(f) {
+		if f.Bool(`hasGet`) {
 			o.L("if t.%s != nil {", f.Name(false))
 			o.L("return t.%s.Get()", f.Name(false))
 			o.L("}")
-			o.L("return %s", codegen.ZeroVal(fieldGetterReturnValue(f)))
+			o.L("return %s", codegen.ZeroVal(rv))
 		} else if !IsPointer(f) {
 			if fieldStorageTypeIsIndirect(f.Type()) {
 				o.L("if t.%s != nil {", f.Name(false))
 				o.L("return *(t.%s)", f.Name(false))
 				o.L("}")
-				o.L("return %s", codegen.ZeroVal(fieldGetterReturnValue(f)))
+				o.L("return %s", codegen.ZeroVal(rv))
 			} else {
 				o.L("return t.%s", f.Name(false))
 			}
@@ -440,7 +363,7 @@ func generateToken(obj *codegen.Object) error {
 	for _, f := range fields {
 		keyName := f.Name(true) + "Key"
 		o.L("if t.%s != nil {", f.Name(false))
-		if fieldHasGet(f) {
+		if f.Bool(`hasGet`) {
 			o.L("v := t.%s.Get()", f.Name(false))
 		} else {
 			if fieldStorageTypeIsIndirect(f.Type()) {
@@ -554,8 +477,6 @@ func generateToken(obj *codegen.Object) error {
 	}
 
 	o.LL("func (t %s) MarshalJSON() ([]byte, error) {", obj.Name(false))
-	o.L("t.mu.RLock()")
-	o.L("defer t.mu.RUnlock()")
 	o.L("buf := pool.GetBytesBuffer()")
 	o.L("defer pool.ReleaseBytesBuffer(buf)")
 	o.L("buf.WriteByte('{')")
@@ -632,18 +553,18 @@ func generateToken(obj *codegen.Object) error {
 	o.L("return iter.AsMap(ctx, t)")
 	o.L("}")
 
-	if err := o.WriteFile(objectFilename(obj), codegen.WithFormatCode(true)); err != nil {
+	if err := o.WriteFile(obj.MustString(`filename`), codegen.WithFormatCode(true)); err != nil {
 		if cfe, ok := err.(codegen.CodeFormatError); ok {
 			fmt.Fprint(os.Stderr, cfe.Source())
 		}
-		return fmt.Errorf(`failed to write to %s: %w`, objectFilename(obj), err)
+		return fmt.Errorf(`failed to write to %s: %w`, obj.MustString(`filename`), err)
 	}
 	return nil
 }
 
 func genBuilder(obj *codegen.Object) error {
 	var buf bytes.Buffer
-	pkg := objectPackage(obj)
+	pkg := obj.MustString(`package`)
 	o := codegen.NewOutput(&buf)
 	o.L("// This file is auto-generated by jwt/internal/cmd/gentoken/main.go. DO NOT EDIT")
 	o.LL("package %s", pkg)


Test patch:
diff --git a/jwt/jwt_test.go b/jwt/jwt_test.go
index c02d8f9c..eff8cdd3 100644
--- a/jwt/jwt_test.go
+++ b/jwt/jwt_test.go
@@ -1291,6 +1291,13 @@ func TestGH430(t *testing.T) {
 	}
 }
 
+func TestGH706(t *testing.T) {
+	tok := jwt.New()
+	if !assert.ErrorIs(t, jwt.ErrMissingRequiredClaim(""), jwt.Validate(tok, jwt.WithRequiredClaim("foo")), `jwt.Validate should fail`) {
+		return
+	}
+}
+
 func TestBenHigginsByPassRegression(t *testing.T) {
 	key, err := rsa.GenerateKey(rand.Reader, 2048)
 	if err != nil {