From 12384ead1e03c0307ee57b05ee2e49cdaa8d30f4 Mon Sep 17 00:00:00 2001 From: Michael Fridman Date: Mon, 31 Mar 2025 11:59:30 -0400 Subject: [PATCH 1/5] Bump GitHub workflows and Go versions (#438) --- .github/workflows/build.yml | 2 +- .github/workflows/lint.yml | 4 ++-- cmd/jwt/main.go | 4 +++- example_test.go | 6 +++--- http_example_test.go | 18 +++++++++--------- parser_test.go | 2 +- validator.go | 2 +- 7 files changed, 20 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 032eb767..7584522f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - go: ["1.21", "1.22", "1.23"] + go: ["1.22", "1.23", "1.24"] steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cec3b92b..fea826a3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,10 +14,10 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: "1.23" + go-version: "1.24" check-latest: true - name: golangci-lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v7 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version version: latest diff --git a/cmd/jwt/main.go b/cmd/jwt/main.go index 22031ca2..4f56850e 100644 --- a/cmd/jwt/main.go +++ b/cmd/jwt/main.go @@ -91,7 +91,9 @@ func loadData(p string) ([]byte, error) { return nil, err } rdr = f - defer f.Close() + if err := f.Close(); err != nil { + return nil, err + } } return io.ReadAll(rdr) } diff --git a/example_test.go b/example_test.go index 651841de..317e54b8 100644 --- a/example_test.go +++ b/example_test.go @@ -90,7 +90,7 @@ func ExampleParseWithClaims_customClaimsType() { if err != nil { log.Fatal(err) } else if claims, ok := token.Claims.(*MyCustomClaims); ok { - fmt.Println(claims.Foo, claims.RegisteredClaims.Issuer) + fmt.Println(claims.Foo, claims.Issuer) } else { log.Fatal("unknown claims type, cannot proceed") } @@ -114,7 +114,7 @@ func ExampleParseWithClaims_validationOptions() { if err != nil { log.Fatal(err) } else if claims, ok := token.Claims.(*MyCustomClaims); ok { - fmt.Println(claims.Foo, claims.RegisteredClaims.Issuer) + fmt.Println(claims.Foo, claims.Issuer) } else { log.Fatal("unknown claims type, cannot proceed") } @@ -153,7 +153,7 @@ func ExampleParseWithClaims_customValidation() { if err != nil { log.Fatal(err) } else if claims, ok := token.Claims.(*MyCustomClaims); ok { - fmt.Println(claims.Foo, claims.RegisteredClaims.Issuer) + fmt.Println(claims.Foo, claims.Issuer) } else { log.Fatal("unknown claims type, cannot proceed") } diff --git a/http_example_test.go b/http_example_test.go index 0b22af93..8e30b14c 100644 --- a/http_example_test.go +++ b/http_example_test.go @@ -92,7 +92,7 @@ func Example_getTokenViaHTTP() { // Read the token out of the response body buf, err := io.ReadAll(res.Body) fatal(err) - res.Body.Close() + _ = res.Body.Close() tokenString := strings.TrimSpace(string(buf)) // Parse the token @@ -104,7 +104,7 @@ func Example_getTokenViaHTTP() { fatal(err) claims := token.Claims.(*CustomClaimsExample) - fmt.Println(claims.CustomerInfo.Name) + fmt.Println(claims.Name) // Output: test } @@ -126,7 +126,7 @@ func Example_useTokenViaHTTP() { // Read the response body buf, err := io.ReadAll(res.Body) fatal(err) - res.Body.Close() + _ = res.Body.Close() fmt.Printf("%s", buf) // Output: Welcome, foo @@ -156,7 +156,7 @@ func authHandler(w http.ResponseWriter, r *http.Request) { // make sure its post if r.Method != "POST" { w.WriteHeader(http.StatusBadRequest) - fmt.Fprintln(w, "No POST", r.Method) + _, _ = fmt.Fprintln(w, "No POST", r.Method) return } @@ -168,21 +168,21 @@ func authHandler(w http.ResponseWriter, r *http.Request) { // check values if user != "test" || pass != "known" { w.WriteHeader(http.StatusForbidden) - fmt.Fprintln(w, "Wrong info") + _, _ = fmt.Fprintln(w, "Wrong info") return } tokenString, err := createToken(user) if err != nil { w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintln(w, "Sorry, error while Signing Token!") + _, _ = fmt.Fprintln(w, "Sorry, error while Signing Token!") log.Printf("Token Signing error: %v\n", err) return } w.Header().Set("Content-Type", "application/jwt") w.WriteHeader(http.StatusOK) - fmt.Fprintln(w, tokenString) + _, _ = fmt.Fprintln(w, tokenString) } // only accessible with a valid token @@ -197,10 +197,10 @@ func restrictedHandler(w http.ResponseWriter, r *http.Request) { // If the token is missing or invalid, return error if err != nil { w.WriteHeader(http.StatusUnauthorized) - fmt.Fprintln(w, "Invalid token:", err) + _, _ = fmt.Fprintln(w, "Invalid token:", err) return } // Token is valid - fmt.Fprintln(w, "Welcome,", token.Claims.(*CustomClaimsExample).Name) + _, _ = fmt.Fprintln(w, "Welcome,", token.Claims.(*CustomClaimsExample).Name) } diff --git a/parser_test.go b/parser_test.go index c0f81711..4e08ef7e 100644 --- a/parser_test.go +++ b/parser_test.go @@ -744,7 +744,7 @@ func TestSetPadding(t *testing.T) { // Parse the token var token *jwt.Token var err error - var opts []jwt.ParserOption = []jwt.ParserOption{jwt.WithoutClaimsValidation()} + var opts = []jwt.ParserOption{jwt.WithoutClaimsValidation()} if data.paddedDecode { opts = append(opts, jwt.WithPaddingAllowed()) diff --git a/validator.go b/validator.go index 008ecd87..7ae70dea 100644 --- a/validator.go +++ b/validator.go @@ -88,7 +88,7 @@ func NewValidator(opts ...ParserOption) *Validator { func (v *Validator) Validate(claims Claims) error { var ( now time.Time - errs []error = make([]error, 0, 6) + errs = make([]error, 0, 6) err error ) From 497a38ec597a28f8f7dd874deb28ad88fb65d0f7 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 9 Apr 2025 14:20:23 +0200 Subject: [PATCH 2/5] Implementing validation of multiple audiences (#433) --- map_claims_test.go | 61 +++++++++++++++++++++++++--------------------- parser_option.go | 25 ++++++++++++++++--- validator.go | 40 +++++++++++++++++++++++------- validator_test.go | 26 +++++++++++--------- 4 files changed, 99 insertions(+), 53 deletions(-) diff --git a/map_claims_test.go b/map_claims_test.go index 034173d2..d78b30ec 100644 --- a/map_claims_test.go +++ b/map_claims_test.go @@ -10,56 +10,61 @@ func TestVerifyAud(t *testing.T) { var nilListInterface []interface{} var intListInterface interface{} = []int{1, 2, 3} type test struct { - Name string - MapClaims MapClaims - Expected bool - Comparison string - Required bool + Name string + MapClaims MapClaims + Expected bool + Comparison []string + MatchAllAud bool + Required bool } tests := []test{ // Matching Claim in aud // Required = true - {Name: "String Aud matching required", MapClaims: MapClaims{"aud": "example.com"}, Expected: true, Required: true, Comparison: "example.com"}, - {Name: "[]String Aud with match required", MapClaims: MapClaims{"aud": []string{"example.com", "example.example.com"}}, Expected: true, Required: true, Comparison: "example.com"}, + {Name: "String Aud matching required", MapClaims: MapClaims{"aud": "example.com"}, Expected: true, Required: true, Comparison: []string{"example.com"}}, + {Name: "[]String Aud with match required", MapClaims: MapClaims{"aud": []string{"example.com", "example.example.com"}}, Expected: true, Required: true, Comparison: []string{"example.com"}}, + {Name: "[]String Aud with []match any required", MapClaims: MapClaims{"aud": []string{"example.com", "example.example.com"}}, Expected: true, Required: true, Comparison: []string{"example.com", "auth.example.com"}}, + {Name: "[]String Aud with []match all required", MapClaims: MapClaims{"aud": []string{"example.com", "example.example.com"}}, Expected: true, Required: true, Comparison: []string{"example.com", "example.example.com"}, MatchAllAud: true}, // Required = false - {Name: "String Aud with match not required", MapClaims: MapClaims{"aud": "example.com"}, Expected: true, Required: false, Comparison: "example.com"}, - {Name: "Empty String Aud with match not required", MapClaims: MapClaims{}, Expected: true, Required: false, Comparison: "example.com"}, - {Name: "Empty String Aud with match not required", MapClaims: MapClaims{"aud": ""}, Expected: true, Required: false, Comparison: "example.com"}, - {Name: "Nil String Aud with match not required", MapClaims: MapClaims{"aud": nil}, Expected: true, Required: false, Comparison: "example.com"}, + {Name: "String Aud with match not required", MapClaims: MapClaims{"aud": "example.com"}, Expected: true, Required: false, Comparison: []string{"example.com"}}, + {Name: "Empty String Aud with match not required", MapClaims: MapClaims{}, Expected: true, Required: false, Comparison: []string{"example.com"}}, + {Name: "Empty String Aud with match not required", MapClaims: MapClaims{"aud": ""}, Expected: true, Required: false, Comparison: []string{"example.com"}}, + {Name: "Nil String Aud with match not required", MapClaims: MapClaims{"aud": nil}, Expected: true, Required: false, Comparison: []string{"example.com"}}, - {Name: "[]String Aud with match not required", MapClaims: MapClaims{"aud": []string{"example.com", "example.example.com"}}, Expected: true, Required: false, Comparison: "example.com"}, - {Name: "Empty []String Aud with match not required", MapClaims: MapClaims{"aud": []string{}}, Expected: true, Required: false, Comparison: "example.com"}, + {Name: "[]String Aud with match not required", MapClaims: MapClaims{"aud": []string{"example.com", "example.example.com"}}, Expected: true, Required: false, Comparison: []string{"example.com"}}, + {Name: "Empty []String Aud with match not required", MapClaims: MapClaims{"aud": []string{}}, Expected: true, Required: false, Comparison: []string{"example.com"}}, // Non-Matching Claim in aud // Required = true - {Name: "String Aud without match required", MapClaims: MapClaims{"aud": "not.example.com"}, Expected: false, Required: true, Comparison: "example.com"}, - {Name: "Empty String Aud without match required", MapClaims: MapClaims{"aud": ""}, Expected: false, Required: true, Comparison: "example.com"}, - {Name: "[]String Aud without match required", MapClaims: MapClaims{"aud": []string{"not.example.com", "example.example.com"}}, Expected: false, Required: true, Comparison: "example.com"}, - {Name: "Empty []String Aud without match required", MapClaims: MapClaims{"aud": []string{""}}, Expected: false, Required: true, Comparison: "example.com"}, - {Name: "String Aud without match not required", MapClaims: MapClaims{"aud": "not.example.com"}, Expected: false, Required: true, Comparison: "example.com"}, - {Name: "Empty String Aud without match not required", MapClaims: MapClaims{"aud": ""}, Expected: false, Required: true, Comparison: "example.com"}, - {Name: "[]String Aud without match not required", MapClaims: MapClaims{"aud": []string{"not.example.com", "example.example.com"}}, Expected: false, Required: true, Comparison: "example.com"}, + {Name: "String Aud without match required", MapClaims: MapClaims{"aud": "not.example.com"}, Expected: false, Required: true, Comparison: []string{"example.com"}}, + {Name: "Empty String Aud without match required", MapClaims: MapClaims{"aud": ""}, Expected: false, Required: true, Comparison: []string{"example.com"}}, + {Name: "[]String Aud without match required", MapClaims: MapClaims{"aud": []string{"not.example.com", "example.example.com"}}, Expected: false, Required: true, Comparison: []string{"example.com"}}, + {Name: "Empty []String Aud without match required", MapClaims: MapClaims{"aud": []string{""}}, Expected: false, Required: true, Comparison: []string{"example.com"}}, + {Name: "String Aud without match not required", MapClaims: MapClaims{"aud": "not.example.com"}, Expected: false, Required: true, Comparison: []string{"example.com"}}, + {Name: "Empty String Aud without match not required", MapClaims: MapClaims{"aud": ""}, Expected: false, Required: true, Comparison: []string{"example.com"}}, + {Name: "[]String Aud without match not required", MapClaims: MapClaims{"aud": []string{"not.example.com", "example.example.com"}}, Expected: false, Required: true, Comparison: []string{"example.com"}}, // Required = false - {Name: "Empty []String Aud without match required", MapClaims: MapClaims{"aud": []string{""}}, Expected: true, Required: false, Comparison: "example.com"}, + {Name: "Empty []String Aud without match required", MapClaims: MapClaims{"aud": []string{""}}, Expected: true, Required: false, Comparison: []string{"example.com"}}, // []interface{} - {Name: "Empty []interface{} Aud without match required", MapClaims: MapClaims{"aud": nilListInterface}, Expected: true, Required: false, Comparison: "example.com"}, - {Name: "[]interface{} Aud with match required", MapClaims: MapClaims{"aud": []interface{}{"a", "foo", "example.com"}}, Expected: true, Required: true, Comparison: "example.com"}, - {Name: "[]interface{} Aud with match but invalid types", MapClaims: MapClaims{"aud": []interface{}{"a", 5, "example.com"}}, Expected: false, Required: true, Comparison: "example.com"}, - {Name: "[]interface{} Aud int with match required", MapClaims: MapClaims{"aud": intListInterface}, Expected: false, Required: true, Comparison: "example.com"}, + {Name: "Empty []interface{} Aud without match required", MapClaims: MapClaims{"aud": nilListInterface}, Expected: true, Required: false, Comparison: []string{"example.com"}}, + {Name: "[]interface{} Aud with match required", MapClaims: MapClaims{"aud": []interface{}{"a", "foo", "example.com"}}, Expected: true, Required: true, Comparison: []string{"example.com"}}, + {Name: "[]interface{} Aud with match but invalid types", MapClaims: MapClaims{"aud": []interface{}{"a", 5, "example.com"}}, Expected: false, Required: true, Comparison: []string{"example.com"}}, + {Name: "[]interface{} Aud int with match required", MapClaims: MapClaims{"aud": intListInterface}, Expected: false, Required: true, Comparison: []string{"example.com"}}, // interface{} - {Name: "Empty interface{} Aud without match not required", MapClaims: MapClaims{"aud": nilInterface}, Expected: true, Required: false, Comparison: "example.com"}, + {Name: "Empty interface{} Aud without match not required", MapClaims: MapClaims{"aud": nilInterface}, Expected: true, Required: false, Comparison: []string{"example.com"}}, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { var opts []ParserOption - if test.Required { - opts = append(opts, WithAudience(test.Comparison)) + if test.Required && test.MatchAllAud { + opts = append(opts, WithAllAudiences(test.Comparison...)) + } else if test.Required { + opts = append(opts, WithAudience(test.Comparison...)) } validator := NewValidator(opts...) diff --git a/parser_option.go b/parser_option.go index 88a780fb..43157355 100644 --- a/parser_option.go +++ b/parser_option.go @@ -66,20 +66,37 @@ func WithExpirationRequired() ParserOption { } } -// WithAudience configures the validator to require the specified audience in -// the `aud` claim. Validation will fail if the audience is not listed in the -// token or the `aud` claim is missing. +// WithAudience configures the validator to require any of the specified +// audiences in the `aud` claim. Validation will fail if the audience is not +// listed in the token or the `aud` claim is missing. // // NOTE: While the `aud` claim is OPTIONAL in a JWT, the handling of it is // application-specific. Since this validation API is helping developers in // writing secure application, we decided to REQUIRE the existence of the claim, // if an audience is expected. -func WithAudience(aud string) ParserOption { +func WithAudience(aud ...string) ParserOption { return func(p *Parser) { p.validator.expectedAud = aud } } +// WithAllAudiences configures the validator to require all the specified +// audiences in the `aud` claim. Validation will fail if the specified audiences +// are not listed in the token or the `aud` claim is missing. Duplicates within +// the list are de-duplicated since internally, we use a map to look up the +// audiences. +// +// NOTE: While the `aud` claim is OPTIONAL in a JWT, the handling of it is +// application-specific. Since this validation API is helping developers in +// writing secure application, we decided to REQUIRE the existence of the claim, +// if an audience is expected. +func WithAllAudiences(aud ...string) ParserOption { + return func(p *Parser) { + p.validator.expectedAud = aud + p.validator.expectAllAud = true + } +} + // WithIssuer configures the validator to require the specified issuer in the // `iss` claim. Validation will fail if a different issuer is specified in the // token or the `iss` claim is missing. diff --git a/validator.go b/validator.go index 7ae70dea..8ac404e5 100644 --- a/validator.go +++ b/validator.go @@ -1,7 +1,6 @@ package jwt import ( - "crypto/subtle" "fmt" "time" ) @@ -52,8 +51,12 @@ type Validator struct { verifyIat bool // expectedAud contains the audience this token expects. Supplying an empty - // string will disable aud checking. - expectedAud string + // slice will disable aud checking. + expectedAud []string + + // expectAllAud specifies whether all expected audiences must be present in + // the token. If false, only one of the expected audiences must be present. + expectAllAud bool // expectedIss contains the issuer this token expects. Supplying an empty // string will disable iss checking. @@ -120,8 +123,8 @@ func (v *Validator) Validate(claims Claims) error { } // If we have an expected audience, we also require the audience claim - if v.expectedAud != "" { - if err = v.verifyAudience(claims, v.expectedAud, true); err != nil { + if len(v.expectedAud) > 0 { + if err = v.verifyAudience(claims, v.expectedAud, v.expectAllAud, true); err != nil { errs = append(errs, err) } } @@ -226,7 +229,7 @@ func (v *Validator) verifyNotBefore(claims Claims, cmp time.Time, required bool) // // Additionally, if any error occurs while retrieving the claim, e.g., when its // the wrong type, an ErrTokenUnverifiable error will be returned. -func (v *Validator) verifyAudience(claims Claims, cmp string, required bool) error { +func (v *Validator) verifyAudience(claims Claims, cmp []string, expectAllAud bool, required bool) error { aud, err := claims.GetAudience() if err != nil { return err @@ -237,16 +240,35 @@ func (v *Validator) verifyAudience(claims Claims, cmp string, required bool) err } // use a var here to keep constant time compare when looping over a number of claims - result := false + matching := make(map[string]bool, 0) + + // build a matching hashmap out of the expected aud + for _, expected := range cmp { + matching[expected] = false + } + // compare the expected aud with the actual aud in a constant time manner by looping over all actual values var stringClaims string for _, a := range aud { - if subtle.ConstantTimeCompare([]byte(a), []byte(cmp)) != 0 { - result = true + a := a + _, ok := matching[a] + if ok { + matching[a] = true } + stringClaims = stringClaims + a } + // check if all expected auds are present + result := true + for _, match := range matching { + if !expectAllAud && match { + break + } else if !match { + result = false + } + } + // case where "" is sent in one or many aud claims if stringClaims == "" { return errorIfRequired(required, "aud") diff --git a/validator_test.go b/validator_test.go index 08a6bd71..df08a539 100644 --- a/validator_test.go +++ b/validator_test.go @@ -22,12 +22,13 @@ func (m MyCustomClaims) Validate() error { func Test_Validator_Validate(t *testing.T) { type fields struct { - leeway time.Duration - timeFunc func() time.Time - verifyIat bool - expectedAud string - expectedIss string - expectedSub string + leeway time.Duration + timeFunc func() time.Time + verifyIat bool + expectedAud []string + expectAllAud bool + expectedIss string + expectedSub string } type args struct { claims Claims @@ -72,12 +73,13 @@ func Test_Validator_Validate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { v := &Validator{ - leeway: tt.fields.leeway, - timeFunc: tt.fields.timeFunc, - verifyIat: tt.fields.verifyIat, - expectedAud: tt.fields.expectedAud, - expectedIss: tt.fields.expectedIss, - expectedSub: tt.fields.expectedSub, + leeway: tt.fields.leeway, + timeFunc: tt.fields.timeFunc, + verifyIat: tt.fields.verifyIat, + expectedAud: tt.fields.expectedAud, + expectAllAud: tt.fields.expectAllAud, + expectedIss: tt.fields.expectedIss, + expectedSub: tt.fields.expectedSub, } if err := v.Validate(tt.args.claims); (err != nil) && !errors.Is(err, tt.wantErr) { t.Errorf("validator.Validate() error = %v, wantErr %v", err, tt.wantErr) From 048854f1b0ac96c0a843d52fc09d7878b853683f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 May 2025 09:48:29 +0200 Subject: [PATCH 3/5] Bump golangci/golangci-lint-action from 7 to 8 (#440) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 7 to 8. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v7...v8) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fea826a3..16279f1d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,7 +17,7 @@ jobs: go-version: "1.24" check-latest: true - name: golangci-lint - uses: golangci/golangci-lint-action@v7 + uses: golangci/golangci-lint-action@v8 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version version: latest From 75740f19088857627364f255f3103a053071ca66 Mon Sep 17 00:00:00 2001 From: Artem Chekhonin <109224492+aachex@users.noreply.github.com> Date: Fri, 13 Jun 2025 02:07:48 +0300 Subject: [PATCH 4/5] replaced interface{} to any (#445) * replaced interface{} to any in KeyFunc * replaced interface{} to its alias any * updated comments and MIGRATION_GUIDE.md file * replaced []any to []interface{} in test name because it is not a comment --- MIGRATION_GUIDE.md | 2 +- cmd/jwt/main.go | 6 +++--- ecdsa.go | 4 ++-- ecdsa_test.go | 10 +++++----- ecdsa_utils.go | 4 ++-- ed25519.go | 4 ++-- ed25519_test.go | 6 +++--- ed25519_utils.go | 4 ++-- example_test.go | 8 ++++---- hmac.go | 4 ++-- hmac_example_test.go | 2 +- hmac_test.go | 10 +++++----- http_example_test.go | 4 ++-- map_claims.go | 8 ++++---- map_claims_test.go | 14 +++++++------- none.go | 4 ++-- none_test.go | 10 +++++----- parser_test.go | 28 ++++++++++++++-------------- request/request_test.go | 2 +- rsa.go | 4 ++-- rsa_pss.go | 4 ++-- rsa_pss_test.go | 10 +++++----- rsa_utils.go | 6 +++--- signing_method.go | 6 +++--- test/helpers.go | 2 +- token.go | 20 ++++++++++---------- token_test.go | 6 +++--- types.go | 4 ++-- 28 files changed, 98 insertions(+), 98 deletions(-) diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index ff9c57e1..b3178e75 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -155,7 +155,7 @@ stored in base64 encoded form, which was redundant with the information in the type Token struct { Raw string // Raw contains the raw token Method SigningMethod // Method is the signing method used or to be used - Header map[string]interface{} // Header is the first segment of the token in decoded form + Header map[string]any // Header is the first segment of the token in decoded form Claims Claims // Claims is the second segment of the token in decoded form Signature []byte // Signature is the third segment of the token in decoded form Valid bool // Valid specifies if the token is valid diff --git a/cmd/jwt/main.go b/cmd/jwt/main.go index 4f56850e..663e7e8b 100644 --- a/cmd/jwt/main.go +++ b/cmd/jwt/main.go @@ -99,7 +99,7 @@ func loadData(p string) ([]byte, error) { } // Print a json object in accordance with the prophecy (or the command line options) -func printJSON(j interface{}) error { +func printJSON(j any) error { var out []byte var err error @@ -132,7 +132,7 @@ func verifyToken() error { } // Parse the token. Load the key from command line option - token, err := jwt.Parse(string(tokData), func(t *jwt.Token) (interface{}, error) { + token, err := jwt.Parse(string(tokData), func(t *jwt.Token) (any, error) { if isNone() { return jwt.UnsafeAllowNoneSignatureType, nil } @@ -196,7 +196,7 @@ func signToken() error { } // get the key - var key interface{} + var key any if isNone() { key = jwt.UnsafeAllowNoneSignatureType } else { diff --git a/ecdsa.go b/ecdsa.go index c929e4a0..06cd94d2 100644 --- a/ecdsa.go +++ b/ecdsa.go @@ -55,7 +55,7 @@ func (m *SigningMethodECDSA) Alg() string { // Verify implements token verification for the SigningMethod. // For this verify method, key must be an ecdsa.PublicKey struct -func (m *SigningMethodECDSA) Verify(signingString string, sig []byte, key interface{}) error { +func (m *SigningMethodECDSA) Verify(signingString string, sig []byte, key any) error { // Get the key var ecdsaKey *ecdsa.PublicKey switch k := key.(type) { @@ -89,7 +89,7 @@ func (m *SigningMethodECDSA) Verify(signingString string, sig []byte, key interf // Sign implements token signing for the SigningMethod. // For this signing method, key must be an ecdsa.PrivateKey struct -func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) ([]byte, error) { +func (m *SigningMethodECDSA) Sign(signingString string, key any) ([]byte, error) { // Get the key var ecdsaKey *ecdsa.PrivateKey switch k := key.(type) { diff --git a/ecdsa_test.go b/ecdsa_test.go index 3caf0a89..989597c4 100644 --- a/ecdsa_test.go +++ b/ecdsa_test.go @@ -15,7 +15,7 @@ var ecdsaTestData = []struct { keys map[string]string tokenString string alg string - claims map[string]interface{} + claims map[string]any valid bool }{ { @@ -23,7 +23,7 @@ var ecdsaTestData = []struct { map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"}, "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJmb28iOiJiYXIifQ.feG39E-bn8HXAKhzDZq7yEAPWYDhZlwTn3sePJnU9VrGMmwdXAIEyoOnrjreYlVM_Z4N13eK9-TmMTWyfKJtHQ", "ES256", - map[string]interface{}{"foo": "bar"}, + map[string]any{"foo": "bar"}, true, }, { @@ -31,7 +31,7 @@ var ecdsaTestData = []struct { map[string]string{"private": "test/ec384-private.pem", "public": "test/ec384-public.pem"}, "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJmb28iOiJiYXIifQ.ngAfKMbJUh0WWubSIYe5GMsA-aHNKwFbJk_wq3lq23aPp8H2anb1rRILIzVR0gUf4a8WzDtrzmiikuPWyCS6CN4-PwdgTk-5nehC7JXqlaBZU05p3toM3nWCwm_LXcld", "ES384", - map[string]interface{}{"foo": "bar"}, + map[string]any{"foo": "bar"}, true, }, { @@ -39,7 +39,7 @@ var ecdsaTestData = []struct { map[string]string{"private": "test/ec512-private.pem", "public": "test/ec512-public.pem"}, "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJmb28iOiJiYXIifQ.AAU0TvGQOcdg2OvrwY73NHKgfk26UDekh9Prz-L_iWuTBIBqOFCWwwLsRiHB1JOddfKAls5do1W0jR_F30JpVd-6AJeTjGKA4C1A1H6gIKwRY0o_tFDIydZCl_lMBMeG5VNFAjO86-WCSKwc3hqaGkq1MugPRq_qrF9AVbuEB4JPLyL5", "ES512", - map[string]interface{}{"foo": "bar"}, + map[string]any{"foo": "bar"}, true, }, { @@ -47,7 +47,7 @@ var ecdsaTestData = []struct { map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"}, "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MEQCIHoSJnmGlPaVQDqacx_2XlXEhhqtWceVopjomc2PJLtdAiAUTeGPoNYxZw0z8mgOnnIcjoxRuNDVZvybRZF3wR1l8W", "ES256", - map[string]interface{}{"foo": "bar"}, + map[string]any{"foo": "bar"}, false, }, } diff --git a/ecdsa_utils.go b/ecdsa_utils.go index 5700636d..44a3b7a1 100644 --- a/ecdsa_utils.go +++ b/ecdsa_utils.go @@ -23,7 +23,7 @@ func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) { } // Parse the key - var parsedKey interface{} + var parsedKey any if parsedKey, err = x509.ParseECPrivateKey(block.Bytes); err != nil { if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { return nil, err @@ -50,7 +50,7 @@ func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) { } // Parse the key - var parsedKey interface{} + var parsedKey any if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { if cert, err := x509.ParseCertificate(block.Bytes); err == nil { parsedKey = cert.PublicKey diff --git a/ed25519.go b/ed25519.go index c2138119..4159e57b 100644 --- a/ed25519.go +++ b/ed25519.go @@ -33,7 +33,7 @@ func (m *SigningMethodEd25519) Alg() string { // Verify implements token verification for the SigningMethod. // For this verify method, key must be an ed25519.PublicKey -func (m *SigningMethodEd25519) Verify(signingString string, sig []byte, key interface{}) error { +func (m *SigningMethodEd25519) Verify(signingString string, sig []byte, key any) error { var ed25519Key ed25519.PublicKey var ok bool @@ -55,7 +55,7 @@ func (m *SigningMethodEd25519) Verify(signingString string, sig []byte, key inte // Sign implements token signing for the SigningMethod. // For this signing method, key must be an ed25519.PrivateKey -func (m *SigningMethodEd25519) Sign(signingString string, key interface{}) ([]byte, error) { +func (m *SigningMethodEd25519) Sign(signingString string, key any) ([]byte, error) { var ed25519Key crypto.Signer var ok bool diff --git a/ed25519_test.go b/ed25519_test.go index e9c7432e..86a79571 100644 --- a/ed25519_test.go +++ b/ed25519_test.go @@ -13,7 +13,7 @@ var ed25519TestData = []struct { keys map[string]string tokenString string alg string - claims map[string]interface{} + claims map[string]any valid bool }{ { @@ -21,7 +21,7 @@ var ed25519TestData = []struct { map[string]string{"private": "test/ed25519-private.pem", "public": "test/ed25519-public.pem"}, "eyJhbGciOiJFRDI1NTE5IiwidHlwIjoiSldUIn0.eyJmb28iOiJiYXIifQ.ESuVzZq1cECrt9Od_gLPVG-_6uRP_8Nq-ajx6CtmlDqRJZqdejro2ilkqaQgSL-siE_3JMTUW7UwAorLaTyFCw", "EdDSA", - map[string]interface{}{"foo": "bar"}, + map[string]any{"foo": "bar"}, true, }, { @@ -29,7 +29,7 @@ var ed25519TestData = []struct { map[string]string{"private": "test/ed25519-private.pem", "public": "test/ed25519-public.pem"}, "eyJhbGciOiJFRDI1NTE5IiwidHlwIjoiSldUIn0.eyJmb28iOiJiYXoifQ.ESuVzZq1cECrt9Od_gLPVG-_6uRP_8Nq-ajx6CtmlDqRJZqdejro2ilkqaQgSL-siE_3JMTUW7UwAorLaTyFCw", "EdDSA", - map[string]interface{}{"foo": "bar"}, + map[string]any{"foo": "bar"}, false, }, } diff --git a/ed25519_utils.go b/ed25519_utils.go index cdb5e68e..6f46e886 100644 --- a/ed25519_utils.go +++ b/ed25519_utils.go @@ -24,7 +24,7 @@ func ParseEdPrivateKeyFromPEM(key []byte) (crypto.PrivateKey, error) { } // Parse the key - var parsedKey interface{} + var parsedKey any if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { return nil, err } @@ -49,7 +49,7 @@ func ParseEdPublicKeyFromPEM(key []byte) (crypto.PublicKey, error) { } // Parse the key - var parsedKey interface{} + var parsedKey any if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { return nil, err } diff --git a/example_test.go b/example_test.go index 317e54b8..846d59ad 100644 --- a/example_test.go +++ b/example_test.go @@ -84,7 +84,7 @@ func ExampleParseWithClaims_customClaimsType() { jwt.RegisteredClaims } - token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (any, error) { return []byte("AllYourBase"), nil }) if err != nil { @@ -108,7 +108,7 @@ func ExampleParseWithClaims_validationOptions() { jwt.RegisteredClaims } - token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (any, error) { return []byte("AllYourBase"), nil }, jwt.WithLeeway(5*time.Second)) if err != nil { @@ -147,7 +147,7 @@ func (m MyCustomClaims) Validate() error { func ExampleParseWithClaims_customValidation() { tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiYXVkIjoic2luZ2xlIn0.QAWg1vGvnqRuCFTMcPkjZljXHh8U3L_qUjszOtQbeaA" - token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (any, error) { return []byte("AllYourBase"), nil }, jwt.WithLeeway(5*time.Second)) if err != nil { @@ -166,7 +166,7 @@ func ExampleParse_errorChecking() { // Token from another example. This token is expired var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c" - token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) { return []byte("AllYourBase"), nil }) diff --git a/hmac.go b/hmac.go index aca600ce..1bef138c 100644 --- a/hmac.go +++ b/hmac.go @@ -55,7 +55,7 @@ func (m *SigningMethodHMAC) Alg() string { // about this, and why we intentionally are not supporting string as a key can // be found on our usage guide // https://golang-jwt.github.io/jwt/usage/signing_methods/#signing-methods-and-key-types. -func (m *SigningMethodHMAC) Verify(signingString string, sig []byte, key interface{}) error { +func (m *SigningMethodHMAC) Verify(signingString string, sig []byte, key any) error { // Verify the key is the right type keyBytes, ok := key.([]byte) if !ok { @@ -88,7 +88,7 @@ func (m *SigningMethodHMAC) Verify(signingString string, sig []byte, key interfa // cryptographically random source, e.g. crypto/rand. Additional information // about this, and why we intentionally are not supporting string as a key can // be found on our usage guide https://golang-jwt.github.io/jwt/usage/signing_methods/. -func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) ([]byte, error) { +func (m *SigningMethodHMAC) Sign(signingString string, key any) ([]byte, error) { if keyBytes, ok := key.([]byte); ok { if !m.Hash.Available() { return nil, ErrHashUnavailable diff --git a/hmac_example_test.go b/hmac_example_test.go index f8f8c26b..dafdd673 100644 --- a/hmac_example_test.go +++ b/hmac_example_test.go @@ -48,7 +48,7 @@ func ExampleParse_hmac() { // useful if you use multiple keys for your application. The standard is to use 'kid' in the // head of the token to identify which key to use, but the parsed token (head and claims) is provided // to the callback, providing flexibility. - token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) { // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") return hmacSampleSecret, nil }, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()})) diff --git a/hmac_test.go b/hmac_test.go index 3eb03804..46900589 100644 --- a/hmac_test.go +++ b/hmac_test.go @@ -13,35 +13,35 @@ var hmacTestData = []struct { name string tokenString string alg string - claims map[string]interface{} + claims map[string]any valid bool }{ { "web sample", "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk", "HS256", - map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, + map[string]any{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, true, }, { "HS384", "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.KWZEuOD5lbBxZ34g7F-SlVLAQ_r5KApWNWlZIIMyQVz5Zs58a7XdNzj5_0EcNoOy", "HS384", - map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, + map[string]any{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, true, }, { "HS512", "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.CN7YijRX6Aw1n2jyI2Id1w90ja-DEMYiWixhYCyHnrZ1VfJRaFQz1bEbjjA5Fn4CLYaUG432dEYmSbS4Saokmw", "HS512", - map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, + map[string]any{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, true, }, { "web sample: invalid", "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXo", "HS256", - map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, + map[string]any{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, false, }, } diff --git a/http_example_test.go b/http_example_test.go index 8e30b14c..827ecd23 100644 --- a/http_example_test.go +++ b/http_example_test.go @@ -96,7 +96,7 @@ func Example_getTokenViaHTTP() { tokenString := strings.TrimSpace(string(buf)) // Parse the token - token, err := jwt.ParseWithClaims(tokenString, &CustomClaimsExample{}, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(tokenString, &CustomClaimsExample{}, func(token *jwt.Token) (any, error) { // since we only use the one private key to sign the tokens, // we also only use its public counter part to verify return verifyKey, nil @@ -188,7 +188,7 @@ func authHandler(w http.ResponseWriter, r *http.Request) { // only accessible with a valid token func restrictedHandler(w http.ResponseWriter, r *http.Request) { // Get token from request - token, err := request.ParseFromRequest(r, request.OAuth2Extractor, func(token *jwt.Token) (interface{}, error) { + token, err := request.ParseFromRequest(r, request.OAuth2Extractor, func(token *jwt.Token) (any, error) { // since we only use the one private key to sign the tokens, // we also only use its public counter part to verify return verifyKey, nil diff --git a/map_claims.go b/map_claims.go index b2b51a1f..3b920527 100644 --- a/map_claims.go +++ b/map_claims.go @@ -5,9 +5,9 @@ import ( "fmt" ) -// MapClaims is a claims type that uses the map[string]interface{} for JSON +// MapClaims is a claims type that uses the map[string]any for JSON // decoding. This is the default claims type if you don't supply one -type MapClaims map[string]interface{} +type MapClaims map[string]any // GetExpirationTime implements the Claims interface. func (m MapClaims) GetExpirationTime() (*NumericDate, error) { @@ -73,7 +73,7 @@ func (m MapClaims) parseClaimsString(key string) (ClaimStrings, error) { cs = append(cs, v) case []string: cs = v - case []interface{}: + case []any: for _, a := range v { vs, ok := a.(string) if !ok { @@ -92,7 +92,7 @@ func (m MapClaims) parseClaimsString(key string) (ClaimStrings, error) { func (m MapClaims) parseString(key string) (string, error) { var ( ok bool - raw interface{} + raw any iss string ) raw, ok = m[key] diff --git a/map_claims_test.go b/map_claims_test.go index d78b30ec..e055b33f 100644 --- a/map_claims_test.go +++ b/map_claims_test.go @@ -6,9 +6,9 @@ import ( ) func TestVerifyAud(t *testing.T) { - var nilInterface interface{} - var nilListInterface []interface{} - var intListInterface interface{} = []int{1, 2, 3} + var nilInterface any + var nilListInterface []any + var intListInterface any = []int{1, 2, 3} type test struct { Name string MapClaims MapClaims @@ -47,13 +47,13 @@ func TestVerifyAud(t *testing.T) { // Required = false {Name: "Empty []String Aud without match required", MapClaims: MapClaims{"aud": []string{""}}, Expected: true, Required: false, Comparison: []string{"example.com"}}, - // []interface{} + // []any {Name: "Empty []interface{} Aud without match required", MapClaims: MapClaims{"aud": nilListInterface}, Expected: true, Required: false, Comparison: []string{"example.com"}}, - {Name: "[]interface{} Aud with match required", MapClaims: MapClaims{"aud": []interface{}{"a", "foo", "example.com"}}, Expected: true, Required: true, Comparison: []string{"example.com"}}, - {Name: "[]interface{} Aud with match but invalid types", MapClaims: MapClaims{"aud": []interface{}{"a", 5, "example.com"}}, Expected: false, Required: true, Comparison: []string{"example.com"}}, + {Name: "[]interface{} Aud with match required", MapClaims: MapClaims{"aud": []any{"a", "foo", "example.com"}}, Expected: true, Required: true, Comparison: []string{"example.com"}}, + {Name: "[]interface{} Aud with match but invalid types", MapClaims: MapClaims{"aud": []any{"a", 5, "example.com"}}, Expected: false, Required: true, Comparison: []string{"example.com"}}, {Name: "[]interface{} Aud int with match required", MapClaims: MapClaims{"aud": intListInterface}, Expected: false, Required: true, Comparison: []string{"example.com"}}, - // interface{} + // any {Name: "Empty interface{} Aud without match not required", MapClaims: MapClaims{"aud": nilInterface}, Expected: true, Required: false, Comparison: []string{"example.com"}}, } diff --git a/none.go b/none.go index 685c2ea3..624ad55e 100644 --- a/none.go +++ b/none.go @@ -25,7 +25,7 @@ func (m *signingMethodNone) Alg() string { } // Only allow 'none' alg type if UnsafeAllowNoneSignatureType is specified as the key -func (m *signingMethodNone) Verify(signingString string, sig []byte, key interface{}) (err error) { +func (m *signingMethodNone) Verify(signingString string, sig []byte, key any) (err error) { // Key must be UnsafeAllowNoneSignatureType to prevent accidentally // accepting 'none' signing method if _, ok := key.(unsafeNoneMagicConstant); !ok { @@ -41,7 +41,7 @@ func (m *signingMethodNone) Verify(signingString string, sig []byte, key interfa } // Only allow 'none' signing if UnsafeAllowNoneSignatureType is specified as the key -func (m *signingMethodNone) Sign(signingString string, key interface{}) ([]byte, error) { +func (m *signingMethodNone) Sign(signingString string, key any) ([]byte, error) { if _, ok := key.(unsafeNoneMagicConstant); ok { return []byte{}, nil } diff --git a/none_test.go b/none_test.go index f126b14a..01e52428 100644 --- a/none_test.go +++ b/none_test.go @@ -12,8 +12,8 @@ var noneTestData = []struct { name string tokenString string alg string - key interface{} - claims map[string]interface{} + key any + claims map[string]any valid bool }{ { @@ -21,7 +21,7 @@ var noneTestData = []struct { "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.", "none", jwt.UnsafeAllowNoneSignatureType, - map[string]interface{}{"foo": "bar"}, + map[string]any{"foo": "bar"}, true, }, { @@ -29,7 +29,7 @@ var noneTestData = []struct { "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.", "none", nil, - map[string]interface{}{"foo": "bar"}, + map[string]any{"foo": "bar"}, false, }, { @@ -37,7 +37,7 @@ var noneTestData = []struct { "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw", "none", jwt.UnsafeAllowNoneSignatureType, - map[string]interface{}{"foo": "bar"}, + map[string]any{"foo": "bar"}, false, }, } diff --git a/parser_test.go b/parser_test.go index 4e08ef7e..96db0cc7 100644 --- a/parser_test.go +++ b/parser_test.go @@ -22,29 +22,29 @@ var ( jwtTestEC256PublicKey crypto.PublicKey jwtTestEC256PrivateKey crypto.PrivateKey paddedKey crypto.PublicKey - defaultKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestDefaultKey, nil } - ecdsaKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestEC256PublicKey, nil } - paddedKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return paddedKey, nil } - emptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, nil } - errorKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, errKeyFuncError } + defaultKeyFunc jwt.Keyfunc = func(t *jwt.Token) (any, error) { return jwtTestDefaultKey, nil } + ecdsaKeyFunc jwt.Keyfunc = func(t *jwt.Token) (any, error) { return jwtTestEC256PublicKey, nil } + paddedKeyFunc jwt.Keyfunc = func(t *jwt.Token) (any, error) { return paddedKey, nil } + emptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (any, error) { return nil, nil } + errorKeyFunc jwt.Keyfunc = func(t *jwt.Token) (any, error) { return nil, errKeyFuncError } nilKeyFunc jwt.Keyfunc = nil - multipleZeroKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return []interface{}{}, nil } - multipleEmptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { + multipleZeroKeyFunc jwt.Keyfunc = func(t *jwt.Token) (any, error) { return []any{}, nil } + multipleEmptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (any, error) { return jwt.VerificationKeySet{Keys: []jwt.VerificationKey{nil, nil}}, nil } - multipleVerificationKeysFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { + multipleVerificationKeysFunc jwt.Keyfunc = func(t *jwt.Token) (any, error) { return []jwt.VerificationKey{jwtTestDefaultKey, jwtTestEC256PublicKey}, nil } - multipleLastKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { + multipleLastKeyFunc jwt.Keyfunc = func(t *jwt.Token) (any, error) { return jwt.VerificationKeySet{Keys: []jwt.VerificationKey{jwtTestEC256PublicKey, jwtTestDefaultKey}}, nil } - multipleFirstKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { + multipleFirstKeyFunc jwt.Keyfunc = func(t *jwt.Token) (any, error) { return jwt.VerificationKeySet{Keys: []jwt.VerificationKey{jwtTestDefaultKey, jwtTestEC256PublicKey}}, nil } - multipleAltTypedKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { + multipleAltTypedKeyFunc jwt.Keyfunc = func(t *jwt.Token) (any, error) { return jwt.VerificationKeySet{Keys: []jwt.VerificationKey{jwtTestDefaultKey, jwtTestDefaultKey}}, nil } - emptyVerificationKeySetFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { + emptyVerificationKeySetFunc jwt.Keyfunc = func(t *jwt.Token) (any, error) { return jwt.VerificationKeySet{}, nil } ) @@ -437,7 +437,7 @@ var jwtTestData = []struct { // signToken creates and returns a signed JWT token using signingMethod. func signToken(claims jwt.Claims, signingMethod jwt.SigningMethod) string { - var privateKey interface{} + var privateKey any switch signingMethod { case jwt.SigningMethodRS256: privateKey = jwtTestRSAPrivateKey @@ -812,7 +812,7 @@ func benchmarkParsing(b *testing.B, parser *jwt.Parser, tokenString string, clai } // Helper method for benchmarking various signing methods -func benchmarkSigning(b *testing.B, method jwt.SigningMethod, key interface{}) { +func benchmarkSigning(b *testing.B, method jwt.SigningMethod, key any) { b.Helper() t := jwt.New(method) b.ReportAllocs() diff --git a/request/request_test.go b/request/request_test.go index 0906d1cf..e8286845 100644 --- a/request/request_test.go +++ b/request/request_test.go @@ -58,7 +58,7 @@ func TestParseRequest(t *testing.T) { // load keys from disk privateKey := test.LoadRSAPrivateKeyFromDisk("../test/sample_key") publicKey := test.LoadRSAPublicKeyFromDisk("../test/sample_key.pub") - keyfunc := func(*jwt.Token) (interface{}, error) { + keyfunc := func(*jwt.Token) (any, error) { return publicKey, nil } diff --git a/rsa.go b/rsa.go index 83cbee6a..98b960a7 100644 --- a/rsa.go +++ b/rsa.go @@ -46,7 +46,7 @@ func (m *SigningMethodRSA) Alg() string { // Verify implements token verification for the SigningMethod // For this signing method, must be an *rsa.PublicKey structure. -func (m *SigningMethodRSA) Verify(signingString string, sig []byte, key interface{}) error { +func (m *SigningMethodRSA) Verify(signingString string, sig []byte, key any) error { var rsaKey *rsa.PublicKey var ok bool @@ -67,7 +67,7 @@ func (m *SigningMethodRSA) Verify(signingString string, sig []byte, key interfac // Sign implements token signing for the SigningMethod // For this signing method, must be an *rsa.PrivateKey structure. -func (m *SigningMethodRSA) Sign(signingString string, key interface{}) ([]byte, error) { +func (m *SigningMethodRSA) Sign(signingString string, key any) ([]byte, error) { var rsaKey *rsa.PrivateKey var ok bool diff --git a/rsa_pss.go b/rsa_pss.go index 28c386ec..7c216ae0 100644 --- a/rsa_pss.go +++ b/rsa_pss.go @@ -82,7 +82,7 @@ func init() { // Verify implements token verification for the SigningMethod. // For this verify method, key must be an rsa.PublicKey struct -func (m *SigningMethodRSAPSS) Verify(signingString string, sig []byte, key interface{}) error { +func (m *SigningMethodRSAPSS) Verify(signingString string, sig []byte, key any) error { var rsaKey *rsa.PublicKey switch k := key.(type) { case *rsa.PublicKey: @@ -108,7 +108,7 @@ func (m *SigningMethodRSAPSS) Verify(signingString string, sig []byte, key inter // Sign implements token signing for the SigningMethod. // For this signing method, key must be an rsa.PrivateKey struct -func (m *SigningMethodRSAPSS) Sign(signingString string, key interface{}) ([]byte, error) { +func (m *SigningMethodRSAPSS) Sign(signingString string, key any) ([]byte, error) { var rsaKey *rsa.PrivateKey switch k := key.(type) { diff --git a/rsa_pss_test.go b/rsa_pss_test.go index 536cde61..cd9b16a0 100644 --- a/rsa_pss_test.go +++ b/rsa_pss_test.go @@ -18,35 +18,35 @@ var rsaPSSTestData = []struct { name string tokenString string alg string - claims map[string]interface{} + claims map[string]any valid bool }{ { "Basic PS256", "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.PPG4xyDVY8ffp4CcxofNmsTDXsrVG2npdQuibLhJbv4ClyPTUtR5giNSvuxo03kB6I8VXVr0Y9X7UxhJVEoJOmULAwRWaUsDnIewQa101cVhMa6iR8X37kfFoiZ6NkS-c7henVkkQWu2HtotkEtQvN5hFlk8IevXXPmvZlhQhwzB1sGzGYnoi1zOfuL98d3BIjUjtlwii5w6gYG2AEEzp7HnHCsb3jIwUPdq86Oe6hIFjtBwduIK90ca4UqzARpcfwxHwVLMpatKask00AgGVI0ysdk0BLMjmLutquD03XbThHScC2C2_Pp4cHWgMzvbgLU2RYYZcZRKr46QeNgz9w", "PS256", - map[string]interface{}{"foo": "bar"}, + map[string]any{"foo": "bar"}, true, }, { "Basic PS384", "eyJhbGciOiJQUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.w7-qqgj97gK4fJsq_DCqdYQiylJjzWONvD0qWWWhqEOFk2P1eDULPnqHRnjgTXoO4HAw4YIWCsZPet7nR3Xxq4ZhMqvKW8b7KlfRTb9cH8zqFvzMmybQ4jv2hKc3bXYqVow3AoR7hN_CWXI3Dv6Kd2X5xhtxRHI6IL39oTVDUQ74LACe-9t4c3QRPuj6Pq1H4FAT2E2kW_0KOc6EQhCLWEhm2Z2__OZskDC8AiPpP8Kv4k2vB7l0IKQu8Pr4RcNBlqJdq8dA5D3hk5TLxP8V5nG1Ib80MOMMqoS3FQvSLyolFX-R_jZ3-zfq6Ebsqr0yEb0AH2CfsECF7935Pa0FKQ", "PS384", - map[string]interface{}{"foo": "bar"}, + map[string]any{"foo": "bar"}, true, }, { "Basic PS512", "eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.GX1HWGzFaJevuSLavqqFYaW8_TpvcjQ8KfC5fXiSDzSiT9UD9nB_ikSmDNyDILNdtjZLSvVKfXxZJqCfefxAtiozEDDdJthZ-F0uO4SPFHlGiXszvKeodh7BuTWRI2wL9-ZO4mFa8nq3GMeQAfo9cx11i7nfN8n2YNQ9SHGovG7_T_AvaMZB_jT6jkDHpwGR9mz7x1sycckEo6teLdHRnH_ZdlHlxqknmyTu8Odr5Xh0sJFOL8BepWbbvIIn-P161rRHHiDWFv6nhlHwZnVzjx7HQrWSGb6-s2cdLie9QL_8XaMcUpjLkfOMKkDOfHo6AvpL7Jbwi83Z2ZTHjJWB-A", "PS512", - map[string]interface{}{"foo": "bar"}, + map[string]any{"foo": "bar"}, true, }, { "basic PS256 invalid: foo => bar", "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.PPG4xyDVY8ffp4CcxofNmsTDXsrVG2npdQuibLhJbv4ClyPTUtR5giNSvuxo03kB6I8VXVr0Y9X7UxhJVEoJOmULAwRWaUsDnIewQa101cVhMa6iR8X37kfFoiZ6NkS-c7henVkkQWu2HtotkEtQvN5hFlk8IevXXPmvZlhQhwzB1sGzGYnoi1zOfuL98d3BIjUjtlwii5w6gYG2AEEzp7HnHCsb3jIwUPdq86Oe6hIFjtBwduIK90ca4UqzARpcfwxHwVLMpatKask00AgGVI0ysdk0BLMjmLutquD03XbThHScC2C2_Pp4cHWgMzvbgLU2RYYZcZRKr46QeNgz9W", "PS256", - map[string]interface{}{"foo": "bar"}, + map[string]any{"foo": "bar"}, false, }, } diff --git a/rsa_utils.go b/rsa_utils.go index b3aeebbe..f22c3d06 100644 --- a/rsa_utils.go +++ b/rsa_utils.go @@ -23,7 +23,7 @@ func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) { return nil, ErrKeyMustBePEMEncoded } - var parsedKey interface{} + var parsedKey any if parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil { if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { return nil, err @@ -53,7 +53,7 @@ func ParseRSAPrivateKeyFromPEMWithPassword(key []byte, password string) (*rsa.Pr return nil, ErrKeyMustBePEMEncoded } - var parsedKey interface{} + var parsedKey any var blockDecrypted []byte if blockDecrypted, err = x509.DecryptPEMBlock(block, []byte(password)); err != nil { @@ -86,7 +86,7 @@ func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) { } // Parse the key - var parsedKey interface{} + var parsedKey any if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { if cert, err := x509.ParseCertificate(block.Bytes); err == nil { parsedKey = cert.PublicKey diff --git a/signing_method.go b/signing_method.go index 0d73631c..096d0ed4 100644 --- a/signing_method.go +++ b/signing_method.go @@ -12,9 +12,9 @@ var signingMethodLock = new(sync.RWMutex) // signature in Sign. The signature is then usually base64 encoded as part of a // JWT. type SigningMethod interface { - Verify(signingString string, sig []byte, key interface{}) error // Returns nil if signature is valid - Sign(signingString string, key interface{}) ([]byte, error) // Returns signature or error - Alg() string // returns the alg identifier for this method (example: 'HS256') + Verify(signingString string, sig []byte, key any) error // Returns nil if signature is valid + Sign(signingString string, key any) ([]byte, error) // Returns signature or error + Alg() string // returns the alg identifier for this method (example: 'HS256') } // RegisterSigningMethod registers the "alg" name and a factory function for signing method. diff --git a/test/helpers.go b/test/helpers.go index 381c5f8a..48235355 100644 --- a/test/helpers.go +++ b/test/helpers.go @@ -33,7 +33,7 @@ func LoadRSAPublicKeyFromDisk(location string) *rsa.PublicKey { } // MakeSampleToken creates and returns a encoded JWT token that has been signed with the specified cryptographic key. -func MakeSampleToken(c jwt.Claims, method jwt.SigningMethod, key interface{}) string { +func MakeSampleToken(c jwt.Claims, method jwt.SigningMethod, key any) string { token := jwt.NewWithClaims(method, c) s, e := token.SignedString(key) diff --git a/token.go b/token.go index 9c7f4ab0..3f715588 100644 --- a/token.go +++ b/token.go @@ -11,9 +11,9 @@ import ( // Token. This allows you to use properties in the Header of the token (such as // `kid`) to identify which key to use. // -// The returned interface{} may be a single key or a VerificationKeySet containing +// The returned any may be a single key or a VerificationKeySet containing // multiple keys. -type Keyfunc func(*Token) (interface{}, error) +type Keyfunc func(*Token) (any, error) // VerificationKey represents a public or secret key for verifying a token's signature. type VerificationKey interface { @@ -28,12 +28,12 @@ type VerificationKeySet struct { // Token represents a JWT Token. Different fields will be used depending on // whether you're creating or parsing/verifying a token. type Token struct { - Raw string // Raw contains the raw token. Populated when you [Parse] a token - Method SigningMethod // Method is the signing method used or to be used - Header map[string]interface{} // Header is the first segment of the token in decoded form - Claims Claims // Claims is the second segment of the token in decoded form - Signature []byte // Signature is the third segment of the token in decoded form. Populated when you Parse a token - Valid bool // Valid specifies if the token is valid. Populated when you Parse/Verify a token + Raw string // Raw contains the raw token. Populated when you [Parse] a token + Method SigningMethod // Method is the signing method used or to be used + Header map[string]any // Header is the first segment of the token in decoded form + Claims Claims // Claims is the second segment of the token in decoded form + Signature []byte // Signature is the third segment of the token in decoded form. Populated when you Parse a token + Valid bool // Valid specifies if the token is valid. Populated when you Parse/Verify a token } // New creates a new [Token] with the specified signing method and an empty map @@ -46,7 +46,7 @@ func New(method SigningMethod, opts ...TokenOption) *Token { // claims. Additional options can be specified, but are currently unused. func NewWithClaims(method SigningMethod, claims Claims, opts ...TokenOption) *Token { return &Token{ - Header: map[string]interface{}{ + Header: map[string]any{ "typ": "JWT", "alg": method.Alg(), }, @@ -60,7 +60,7 @@ func NewWithClaims(method SigningMethod, claims Claims, opts ...TokenOption) *To // https://golang-jwt.github.io/jwt/usage/signing_methods/#signing-methods-and-key-types // for an overview of the different signing methods and their respective key // types. -func (t *Token) SignedString(key interface{}) (string, error) { +func (t *Token) SignedString(key any) (string, error) { sstr, err := t.SigningString() if err != nil { return "", err diff --git a/token_test.go b/token_test.go index f18329e0..b54334a4 100644 --- a/token_test.go +++ b/token_test.go @@ -10,7 +10,7 @@ func TestToken_SigningString(t1 *testing.T) { type fields struct { Raw string Method jwt.SigningMethod - Header map[string]interface{} + Header map[string]any Claims jwt.Claims Signature []byte Valid bool @@ -26,7 +26,7 @@ func TestToken_SigningString(t1 *testing.T) { fields: fields{ Raw: "", Method: jwt.SigningMethodHS256, - Header: map[string]interface{}{ + Header: map[string]any{ "typ": "JWT", "alg": jwt.SigningMethodHS256.Alg(), }, @@ -62,7 +62,7 @@ func TestToken_SigningString(t1 *testing.T) { func BenchmarkToken_SigningString(b *testing.B) { t := &jwt.Token{ Method: jwt.SigningMethodHS256, - Header: map[string]interface{}{ + Header: map[string]any{ "typ": "JWT", "alg": jwt.SigningMethodHS256.Alg(), }, diff --git a/types.go b/types.go index b2655a9e..a3e0ef12 100644 --- a/types.go +++ b/types.go @@ -103,7 +103,7 @@ func (date *NumericDate) UnmarshalJSON(b []byte) (err error) { type ClaimStrings []string func (s *ClaimStrings) UnmarshalJSON(data []byte) (err error) { - var value interface{} + var value any if err = json.Unmarshal(data, &value); err != nil { return err @@ -116,7 +116,7 @@ func (s *ClaimStrings) UnmarshalJSON(data []byte) (err error) { aud = append(aud, v) case []string: aud = ClaimStrings(v) - case []interface{}: + case []any: for _, vv := range v { vs, ok := vv.(string) if !ok { From d83e545cee83663ad15137c8615900a60447baad Mon Sep 17 00:00:00 2001 From: sfinnman-cotn Date: Tue, 1 Jul 2025 15:04:53 +0200 Subject: [PATCH 5/5] Fix bug in validation of multiple audiences (#441) * Fix bug in validation of multiple audiences In a situation where multiple audiences are validated by the validator, the order of evaluation of the for-range loop affects the result. If we produce matches such as: ``` { "example.org": true, "example.com": false, } ``` and we configured the validator to expect a single match on audience, the code would either: 1. produce "token has invalid audience" if "example.org" was evaluated first 2. produce a passing result if "example.com" was evaluated first This commit fixes this bug, and adds a suite of tests as well as regression tests to prevent this issue in future. * Adding three more test cases to be sure * Removing required alltogether form verifyAudience * Removing required --------- Co-authored-by: Christian Banse --- validator.go | 52 ++++++++------------- validator_test.go | 113 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 32 deletions(-) diff --git a/validator.go b/validator.go index 8ac404e5..92b5c057 100644 --- a/validator.go +++ b/validator.go @@ -2,6 +2,7 @@ package jwt import ( "fmt" + "slices" "time" ) @@ -124,7 +125,7 @@ func (v *Validator) Validate(claims Claims) error { // If we have an expected audience, we also require the audience claim if len(v.expectedAud) > 0 { - if err = v.verifyAudience(claims, v.expectedAud, v.expectAllAud, true); err != nil { + if err = v.verifyAudience(claims, v.expectedAud, v.expectAllAud); err != nil { errs = append(errs, err) } } @@ -229,52 +230,39 @@ func (v *Validator) verifyNotBefore(claims Claims, cmp time.Time, required bool) // // Additionally, if any error occurs while retrieving the claim, e.g., when its // the wrong type, an ErrTokenUnverifiable error will be returned. -func (v *Validator) verifyAudience(claims Claims, cmp []string, expectAllAud bool, required bool) error { +func (v *Validator) verifyAudience(claims Claims, cmp []string, expectAllAud bool) error { aud, err := claims.GetAudience() if err != nil { return err } - if len(aud) == 0 { + // Check that aud exists and is not empty. We only require the aud claim + // if we expect at least one audience to be present. + if len(aud) == 0 || len(aud) == 1 && aud[0] == "" { + required := len(v.expectedAud) > 0 return errorIfRequired(required, "aud") } - // use a var here to keep constant time compare when looping over a number of claims - matching := make(map[string]bool, 0) - - // build a matching hashmap out of the expected aud - for _, expected := range cmp { - matching[expected] = false - } - - // compare the expected aud with the actual aud in a constant time manner by looping over all actual values - var stringClaims string - for _, a := range aud { - a := a - _, ok := matching[a] - if ok { - matching[a] = true + if !expectAllAud { + for _, a := range aud { + // If we only expect one match, we can stop early if we find a match + if slices.Contains(cmp, a) { + return nil + } } - stringClaims = stringClaims + a + return ErrTokenInvalidAudience } - // check if all expected auds are present - result := true - for _, match := range matching { - if !expectAllAud && match { - break - } else if !match { - result = false + // Note that we are looping cmp here to ensure that all expected audiences + // are present in the aud claim. + for _, a := range cmp { + if !slices.Contains(aud, a) { + return ErrTokenInvalidAudience } } - // case where "" is sent in one or many aud claims - if stringClaims == "" { - return errorIfRequired(required, "aud") - } - - return errorIfFalse(result, ErrTokenInvalidAudience) + return nil } // verifyIssuer compares the iss claim in claims against cmp. diff --git a/validator_test.go b/validator_test.go index df08a539..9fdaafab 100644 --- a/validator_test.go +++ b/validator_test.go @@ -261,3 +261,116 @@ func Test_Validator_verifyIssuedAt(t *testing.T) { }) } } + +func Test_Validator_verifyAudience(t *testing.T) { + type fields struct { + expectedAud []string + } + type args struct { + claims Claims + cmp []string + expectAllAud bool + } + tests := []struct { + name string + fields fields + args args + wantErr error + }{ + { + name: "fail without audience when expecting one aud match", + fields: fields{expectedAud: []string{"example.com"}}, + args: args{ + claims: MapClaims{}, + cmp: []string{"example.com"}, + expectAllAud: false, + }, + wantErr: ErrTokenRequiredClaimMissing, + }, + { + name: "fail without audience when expecting all aud matches", + fields: fields{expectedAud: []string{"example.com"}}, + args: args{ + claims: MapClaims{}, + cmp: []string{"example.com"}, + expectAllAud: true, + }, + wantErr: ErrTokenRequiredClaimMissing, + }, + { + name: "good when audience matches", + fields: fields{expectedAud: []string{"example.com"}}, + args: args{ + claims: RegisteredClaims{Audience: ClaimStrings{"example.com"}}, + cmp: []string{"example.com"}, + expectAllAud: false, + }, + wantErr: nil, + }, + { + name: "fail when audience matches with one value", + fields: fields{expectedAud: []string{"example.org", "example.com"}}, + args: args{ + claims: RegisteredClaims{Audience: ClaimStrings{"example.com"}}, + cmp: []string{"example.org", "example.com"}, + expectAllAud: false, + }, + wantErr: nil, + }, + { + name: "fail when audience matches with all values", + fields: fields{expectedAud: []string{"example.org", "example.com"}}, + args: args{ + claims: RegisteredClaims{Audience: ClaimStrings{"example.org", "example.com"}}, + cmp: []string{"example.org", "example.com"}, + expectAllAud: true, + }, + wantErr: nil, + }, + { + name: "fail when audience not matching", + fields: fields{expectedAud: []string{"example.org", "example.com"}}, + args: args{ + claims: RegisteredClaims{Audience: ClaimStrings{"example.net"}}, + cmp: []string{"example.org", "example.com"}, + expectAllAud: false, + }, + wantErr: ErrTokenInvalidAudience, + }, + { + name: "fail when audience not matching all values", + fields: fields{expectedAud: []string{"example.org", "example.com"}}, + args: args{ + claims: RegisteredClaims{Audience: ClaimStrings{"example.org", "example.net"}}, + cmp: []string{"example.org", "example.com"}, + expectAllAud: true, + }, + wantErr: ErrTokenInvalidAudience, + }, + { + name: "fail when audience missing", + fields: fields{expectedAud: []string{"example.org", "example.com"}}, + args: args{ + claims: MapClaims{}, + cmp: []string{"example.org", "example.com"}, + expectAllAud: true, + }, + wantErr: ErrTokenRequiredClaimMissing, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &Validator{ + expectedAud: tt.fields.expectedAud, + expectAllAud: tt.args.expectAllAud, + } + + err := v.verifyAudience(tt.args.claims, tt.args.cmp, tt.args.expectAllAud) + if tt.wantErr == nil && err != nil { + t.Errorf("validator.verifyAudience() error = %v, wantErr %v", err, tt.wantErr) + } else if tt.wantErr != nil && !errors.Is(err, tt.wantErr) { + t.Errorf("validator.verifyAudience() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +}