Skip to content

Commit 7243385

Browse files
authored
[jwt] implement distinguishable jwt.Get errors (#1426)
* [jwt] implement distinguishable jwt.Get errors * appease linter * Fix bazel errors * Update jwt/jwt_test.go * Update jwt/jwt_test.go * Update Changes
1 parent 36056b8 commit 7243385

12 files changed

Lines changed: 371 additions & 246 deletions

File tree

Changes

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ Changes
44
v3 has many incompatibilities with v2. To see the full list of differences between
55
v2 and v3, please read the Changes-v3.md file (https://github.com/lestrrat-go/jwx/blob/develop/v3/Changes-v3.md)
66

7+
v3.0.9 UNRELEASED
8+
* [jwt] `(jwt.Token).Get` methods now return specific types of errors depending
9+
on if a) the specified claim was not present, or b) the specified claim could
10+
not be assigned to the destination variable.
11+
12+
You can distinguish these by using `errors.Is` against `jwt.ClaimNotFoundError()`
13+
or `jwt.ClaimAssignmentFailedError()`
14+
715
v3.0.8 27 Jun 2025
816
* [jwe/jwebb] (EXPERIMENTAL) Add low-level functions for JWE operations.
917
* [jws/jwsbb] (EXPERIMENTAL/BREAKS COMPATIBILITY) Add io.Reader parameter

jwt/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ go_library(
3434
"//jws",
3535
"//jws/jwsbb",
3636
"//jwt/internal/types",
37+
"//jwt/internal/errors",
3738
"@com_github_lestrrat_go_blackmagic//:blackmagic",
3839
"@com_github_lestrrat_go_option_v2//:option",
3940
],

jwt/errors.go

Lines changed: 43 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -1,213 +1,95 @@
11
package jwt
22

33
import (
4-
"errors"
5-
"fmt"
4+
jwterrs "github.com/lestrrat-go/jwx/v3/jwt/internal/errors"
65
)
76

8-
var errUnknownPayloadType = errors.New(`unknown payload type (payload is not JWT?)`)
7+
// ClaimNotFoundError returns the opaque error value that is returned when
8+
// `jwt.Get` fails to find the requested claim.
9+
//
10+
// This value should only be used for comparison using `errors.Is()`.
11+
func ClaimNotFoundError() error {
12+
return jwterrs.ErrClaimNotFound
13+
}
14+
15+
// ClaimAssignmentFailedError returns the opaque error value that is returned
16+
// when `jwt.Get` fails to assign the value to the destination. For example,
17+
// this can happen when the value is a string, but you passed a &int as the
18+
// destination.
19+
//
20+
// This value should only be used for comparison using `errors.Is()`.
21+
func ClaimAssignmentFailedError() error {
22+
return jwterrs.ErrClaimAssignmentFailed
23+
}
924

1025
// UnknownPayloadTypeError returns the opaque error value that is returned when
1126
// `jwt.Parse` fails due to not being able to deduce the format of
12-
// the incoming buffer
27+
// the incoming buffer.
28+
//
29+
// This value should only be used for comparison using `errors.Is()`.
1330
func UnknownPayloadTypeError() error {
14-
return errUnknownPayloadType
15-
}
16-
17-
type parseError struct {
18-
error
31+
return jwterrs.ErrUnknownPayloadType
1932
}
2033

21-
var errDefaultParseError = parseerr(`jwt.Parse`, `unknown error`)
22-
23-
// ParseError returns an error that can be passed to `errors.Is` to check if the error is a parse error.
34+
// ParseError returns the opaque error that is returned from jwt.Parse when
35+
// the input is not a valid JWT.
36+
//
37+
// This value should only be used for comparison using `errors.Is()`.
2438
func ParseError() error {
25-
return errDefaultParseError
39+
return jwterrs.ErrParse
2640
}
2741

28-
func (e parseError) Unwrap() error {
29-
return e.error
30-
}
31-
32-
func (parseError) Is(err error) bool {
33-
_, ok := err.(parseError)
34-
return ok
35-
}
36-
37-
func parseerr(prefix string, f string, args ...any) error {
38-
return parseError{fmt.Errorf(prefix+": "+f, args...)}
39-
}
40-
41-
type validationError struct {
42-
error
43-
}
44-
45-
var errDefaultValidateError = validateerr(`unknown error`)
46-
42+
// ValidateError returns the immutable error used for validation errors
43+
//
44+
// This value should only be used for comparison using `errors.Is()`.
4745
func ValidateError() error {
48-
return errDefaultValidateError
49-
}
50-
51-
func (validationError) Is(err error) bool {
52-
_, ok := err.(validationError)
53-
return ok
54-
}
55-
56-
func (err validationError) Unwrap() error {
57-
return err.error
58-
}
59-
60-
func validateerr(f string, args ...any) error {
61-
return validationError{fmt.Errorf(`jwt.Validate: `+f, args...)}
62-
}
63-
64-
type invalidIssuerError struct {
65-
error
46+
return jwterrs.ErrValidateDefault
6647
}
6748

68-
func (err invalidIssuerError) Is(target error) bool {
69-
_, ok := target.(invalidIssuerError)
70-
return ok
71-
}
72-
73-
func (err invalidIssuerError) Unwrap() error {
74-
return err.error
75-
}
76-
77-
func issuererr(f string, args ...any) error {
78-
return invalidIssuerError{fmt.Errorf(`"iss" not satisfied: `+f, args...)}
79-
}
80-
81-
var errDefaultInvalidIssuer = invalidIssuerError{errors.New(`"iss" not satisfied`)}
82-
8349
// InvalidIssuerError returns the immutable error used when `iss` claim
8450
// is not satisfied
8551
//
86-
// The return value should only be used for comparison using `errors.Is()`
52+
// This value should only be used for comparison using `errors.Is()`.
8753
func InvalidIssuerError() error {
88-
return errDefaultInvalidIssuer
89-
}
90-
91-
type tokenExpiredError struct {
92-
error
54+
return jwterrs.ErrInvalidIssuerDefault
9355
}
9456

95-
func (err tokenExpiredError) Is(target error) bool {
96-
_, ok := target.(tokenExpiredError)
97-
return ok
98-
}
99-
100-
func (err tokenExpiredError) Unwrap() error {
101-
return err.error
102-
}
103-
104-
var errDefaultTokenExpired = tokenExpiredError{errors.New(`"exp" not satisfied: token is expired`)}
105-
10657
// TokenExpiredError returns the immutable error used when `exp` claim
10758
// is not satisfied.
10859
//
109-
// The return value should only be used for comparison using `errors.Is()`
60+
// This value should only be used for comparison using `errors.Is()`.
11061
func TokenExpiredError() error {
111-
return errDefaultTokenExpired
112-
}
113-
114-
type invalidIssuedAtError struct {
115-
error
116-
}
117-
118-
func (err invalidIssuedAtError) Is(target error) bool {
119-
_, ok := target.(invalidIssuedAtError)
120-
return ok
62+
return jwterrs.ErrTokenExpiredDefault
12163
}
12264

123-
func (err invalidIssuedAtError) Unwrap() error {
124-
return err.error
125-
}
126-
127-
var errDefaultInvalidIssuedAt = invalidIssuedAtError{errors.New(`"iat" not satisfied`)}
128-
12965
// InvalidIssuedAtError returns the immutable error used when `iat` claim
13066
// is not satisfied
13167
//
132-
// The return value should only be used for comparison using `errors.Is()`
68+
// This value should only be used for comparison using `errors.Is()`.
13369
func InvalidIssuedAtError() error {
134-
return errDefaultInvalidIssuedAt
135-
}
136-
137-
type tokenNotYetValidError struct {
138-
error
139-
}
140-
141-
func (err tokenNotYetValidError) Is(target error) bool {
142-
_, ok := target.(tokenNotYetValidError)
143-
return ok
144-
}
145-
146-
func (err tokenNotYetValidError) Unwrap() error {
147-
return err.error
70+
return jwterrs.ErrInvalidIssuedAtDefault
14871
}
14972

150-
var errDefaultTokenNotYetValid = tokenNotYetValidError{errors.New(`"nbf" not satisfied: token is not yet valid`)}
151-
15273
// TokenNotYetValidError returns the immutable error used when `nbf` claim
15374
// is not satisfied
15475
//
155-
// The return value should only be used for comparison using `errors.Is()`
76+
// This value should only be used for comparison using `errors.Is()`.
15677
func TokenNotYetValidError() error {
157-
return errDefaultTokenNotYetValid
158-
}
159-
160-
type invalidAudienceError struct {
161-
error
162-
}
163-
164-
func (err invalidAudienceError) Is(target error) bool {
165-
_, ok := target.(invalidAudienceError)
166-
return ok
167-
}
168-
169-
func (err invalidAudienceError) Unwrap() error {
170-
return err.error
171-
}
172-
173-
func auderr(f string, args ...any) error {
174-
return invalidAudienceError{fmt.Errorf(`"aud" not satisfied: `+f, args...)}
78+
return jwterrs.ErrTokenNotYetValidDefault
17579
}
17680

177-
var errDefaultInvalidAudience = invalidAudienceError{errors.New(`"aud" not satisfied`)}
178-
17981
// InvalidAudienceError returns the immutable error used when `aud` claim
18082
// is not satisfied
18183
//
182-
// The return value should only be used for comparison using `errors.Is()`
84+
// This value should only be used for comparison using `errors.Is()`.
18385
func InvalidAudienceError() error {
184-
return errDefaultInvalidAudience
185-
}
186-
187-
type missingRequiredClaimError struct {
188-
error
189-
190-
claim string
191-
}
192-
193-
func (err *missingRequiredClaimError) Is(target error) bool {
194-
err1, ok := target.(*missingRequiredClaimError)
195-
if !ok {
196-
return false
197-
}
198-
return err1 == errDefaultMissingRequiredClaim || err1.claim == err.claim
199-
}
200-
201-
var errDefaultMissingRequiredClaim = &missingRequiredClaimError{error: errors.New(`required claim is missing`)}
202-
203-
func errMissingRequiredClaim(name string) error {
204-
return &missingRequiredClaimError{claim: name, error: fmt.Errorf(`required claim "%s" is missing`, name)}
86+
return jwterrs.ErrInvalidAudienceDefault
20587
}
20688

20789
// MissingRequiredClaimError returns the immutable error used when the claim
20890
// specified by `jwt.IsRequired()` is not present.
20991
//
210-
// The return value should only be used for comparison using `errors.Is()`
92+
// This value should only be used for comparison using `errors.Is()`.
21193
func MissingRequiredClaimError() error {
212-
return errDefaultMissingRequiredClaim
94+
return jwterrs.ErrMissingRequiredClaimDefault
21395
}

jwt/internal/errors/BUILD.bazel

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library")
2+
3+
go_library(
4+
name = "errors",
5+
srcs = [
6+
"errors.go",
7+
],
8+
importpath = "github.com/lestrrat-go/jwx/v3/jwt/internal/errors",
9+
visibility = ["//jwt:__subpackages__"],
10+
)
11+
12+
alias(
13+
name = "go_default_library",
14+
actual = ":errors",
15+
visibility = ["//jwt:__subpackages__"],
16+
)

0 commit comments

Comments
 (0)