Skip to content

Commit 3a9ee81

Browse files
authored
Cleanup and documentation of verification functions (#262)
This PR adds further documentation to the validator and does an additional cleanup to make the VerifyXXX functions more managable.
1 parent 1ef0fe8 commit 3a9ee81

File tree

3 files changed

+95
-94
lines changed

3 files changed

+95
-94
lines changed

errors.go

+13-6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ var (
2222
ErrTokenNotValidYet = errors.New("token is not valid yet")
2323
ErrTokenInvalidId = errors.New("token has invalid id")
2424
ErrTokenInvalidClaims = errors.New("token has invalid claims")
25+
26+
ErrInvalidType = errors.New("invalid type for claim")
2527
)
2628

2729
// The errors that might occur when parsing and validating a token
@@ -51,9 +53,12 @@ func NewValidationError(errorText string, errorFlags uint32) *ValidationError {
5153

5254
// ValidationError represents an error from Parse if token is not valid
5355
type ValidationError struct {
54-
Inner error // stores the error returned by external dependencies, i.e.: KeyFunc
55-
Errors uint32 // bitfield. see ValidationError... constants
56-
text string // errors that do not have a valid error just have text
56+
// Inner stores the error returned by external dependencies, e.g.: KeyFunc
57+
Inner error
58+
// Errors is a bit-field. See ValidationError... constants
59+
Errors uint32
60+
// Text can be used for errors that do not have a valid error just have text
61+
text string
5762
}
5863

5964
// Error is the implementation of the err interface.
@@ -77,9 +82,11 @@ func (e *ValidationError) valid() bool {
7782
return e.Errors == 0
7883
}
7984

80-
// Is checks if this ValidationError is of the supplied error. We are first checking for the exact error message
81-
// by comparing the inner error message. If that fails, we compare using the error flags. This way we can use
82-
// custom error messages (mainly for backwards compatability) and still leverage errors.Is using the global error variables.
85+
// Is checks if this ValidationError is of the supplied error. We are first
86+
// checking for the exact error message by comparing the inner error message. If
87+
// that fails, we compare using the error flags. This way we can use custom
88+
// error messages (mainly for backwards compatibility) and still leverage
89+
// errors.Is using the global error variables.
8390
func (e *ValidationError) Is(err error) bool {
8491
// Check, if our inner error is a direct match
8592
if errors.Is(errors.Unwrap(e), err) {

map_claims.go

-3
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,12 @@ package jwt
22

33
import (
44
"encoding/json"
5-
"errors"
65
)
76

87
// MapClaims is a claims type that uses the map[string]interface{} for JSON decoding.
98
// This is the default claims type if you don't supply one
109
type MapClaims map[string]interface{}
1110

12-
var ErrInvalidType = errors.New("invalid type for claim")
13-
1411
// GetExpirationTime implements the Claims interface.
1512
func (m MapClaims) GetExpirationTime() (*NumericDate, error) {
1613
return m.ParseNumericDate("exp")

validator.go

+82-85
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ func newValidator(opts ...ParserOption) *validator {
5353
return p.validator
5454
}
5555

56-
// Validate validates the given claims. It will also perform any custom validation if claims implements the CustomValidator interface.
56+
// Validate validates the given claims. It will also perform any custom
57+
// validation if claims implements the CustomValidator interface.
5758
func (v *validator) Validate(claims Claims) error {
5859
var now time.Time
5960
vErr := new(ValidationError)
@@ -65,13 +66,15 @@ func (v *validator) Validate(claims Claims) error {
6566
now = time.Now()
6667
}
6768

68-
// We always need to check the expiration time, but the claim itself is OPTIONAL
69+
// We always need to check the expiration time, but usage of the claim
70+
// itself is OPTIONAL
6971
if !v.VerifyExpiresAt(claims, now, false) {
7072
vErr.Inner = ErrTokenExpired
7173
vErr.Errors |= ValidationErrorExpired
7274
}
7375

74-
// We always need to check not-before, but the claim itself is OPTIONAL
76+
// We always need to check not-before, but usage of the claim itself is
77+
// OPTIONAL
7578
if !v.VerifyNotBefore(claims, now, false) {
7679
vErr.Inner = ErrTokenNotValidYet
7780
vErr.Errors |= ValidationErrorNotValidYet
@@ -102,7 +105,7 @@ func (v *validator) Validate(claims Claims) error {
102105
}
103106

104107
// Finally, we want to give the claim itself some possibility to do some
105-
// additional custom validation based on their custom claims
108+
// additional custom validation based on a custom function
106109
cvt, ok := claims.(CustomClaims)
107110
if ok {
108111
if err := cvt.CustomValidation(); err != nil {
@@ -118,90 +121,86 @@ func (v *validator) Validate(claims Claims) error {
118121
return vErr
119122
}
120123

121-
// VerifyAudience compares the aud claim against cmp.
122-
// If required is false, this method will return true if the value matches or is unset
123-
func (v *validator) VerifyAudience(claims Claims, cmp string, req bool) bool {
124-
aud, err := claims.GetAudience()
125-
if err != nil {
126-
return false
127-
}
128-
129-
return verifyAud(aud, cmp, req)
130-
}
131-
132-
// VerifyExpiresAt compares the exp claim against cmp (cmp < exp).
133-
// If req is false, it will return true, if exp is unset.
134-
func (v *validator) VerifyExpiresAt(claims Claims, cmp time.Time, req bool) bool {
135-
var time *time.Time = nil
136-
124+
// VerifyExpiresAt compares the exp claim in claims against cmp. This function
125+
// will return true if cmp < exp. Additional leeway is taken into account.
126+
//
127+
// If exp is not set, it will return true if the claim is not required,
128+
// otherwise false will be returned.
129+
//
130+
// Additionally, if any error occurs while retrieving the claim, e.g., when its
131+
// the wrong type, false will be returned.
132+
func (v *validator) VerifyExpiresAt(claims Claims, cmp time.Time, required bool) bool {
137133
exp, err := claims.GetExpirationTime()
138134
if err != nil {
139135
return false
140-
} else if exp != nil {
141-
time = &exp.Time
142136
}
143137

144-
return verifyExp(time, cmp, req, v.leeway)
138+
if exp != nil {
139+
return cmp.Before((exp.Time).Add(+v.leeway))
140+
} else {
141+
return !required
142+
}
145143
}
146144

147-
// VerifyIssuedAt compares the iat claim against cmp (cmp >= iat).
148-
// If req is false, it will return true, if iat is unset.
149-
func (v *validator) VerifyIssuedAt(claims Claims, cmp time.Time, req bool) bool {
150-
var time *time.Time = nil
151-
145+
// VerifyIssuedAt compares the iat claim in claims against cmp. This function
146+
// will return true if cmp >= iat. Additional leeway is taken into account.
147+
//
148+
// If iat is not set, it will return true if the claim is not required,
149+
// otherwise false will be returned.
150+
//
151+
// Additionally, if any error occurs while retrieving the claim, e.g., when its
152+
// the wrong type, false will be returned.
153+
func (v *validator) VerifyIssuedAt(claims Claims, cmp time.Time, required bool) bool {
152154
iat, err := claims.GetIssuedAt()
153155
if err != nil {
154156
return false
155-
} else if iat != nil {
156-
time = &iat.Time
157157
}
158158

159-
return verifyIat(time, cmp, req, v.leeway)
159+
if iat != nil {
160+
return !cmp.Before(iat.Add(-v.leeway))
161+
} else {
162+
return !required
163+
}
160164
}
161165

162-
// VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf).
163-
// If req is false, it will return true, if nbf is unset.
164-
func (v *validator) VerifyNotBefore(claims Claims, cmp time.Time, req bool) bool {
165-
var time *time.Time = nil
166-
166+
// VerifyNotBefore compares the nbf claim in claims against cmp. This function
167+
// will return true if cmp >= nbf. Additional leeway is taken into account.
168+
//
169+
// If nbf is not set, it will return true if the claim is not required,
170+
// otherwise false will be returned.
171+
//
172+
// Additionally, if any error occurs while retrieving the claim, e.g., when its
173+
// the wrong type, false will be returned.
174+
func (v *validator) VerifyNotBefore(claims Claims, cmp time.Time, required bool) bool {
167175
nbf, err := claims.GetNotBefore()
168176
if err != nil {
169177
return false
170-
} else if nbf != nil {
171-
time = &nbf.Time
172178
}
173179

174-
return verifyNbf(time, cmp, req, v.leeway)
175-
}
176-
177-
// VerifyIssuer compares the iss claim against cmp.
178-
// If required is false, this method will return true if the value matches or is unset
179-
func (v *validator) VerifyIssuer(claims Claims, cmp string, req bool) bool {
180-
iss, err := claims.GetIssuer()
181-
if err != nil {
182-
return false
180+
if nbf != nil {
181+
return !cmp.Before(nbf.Add(-v.leeway))
182+
} else {
183+
return !required
183184
}
184-
185-
return verifyIss(iss, cmp, req)
186185
}
187186

188-
// VerifySubject compares the sub claim against cmp.
189-
// If required is false, this method will return true if the value matches or is unset
190-
func (v *validator) VerifySubject(claims Claims, cmp string, req bool) bool {
191-
iss, err := claims.GetSubject()
187+
// VerifyAudience compares the aud claim against cmp.
188+
//
189+
// If aud is not set or an empty list, it will return true if the claim is not
190+
// required, otherwise false will be returned.
191+
//
192+
// Additionally, if any error occurs while retrieving the claim, e.g., when its
193+
// the wrong type, false will be returned.
194+
func (v *validator) VerifyAudience(claims Claims, cmp string, required bool) bool {
195+
aud, err := claims.GetAudience()
192196
if err != nil {
193197
return false
194198
}
195199

196-
return verifySub(iss, cmp, req)
197-
}
198-
199-
// ----- helpers
200-
201-
func verifyAud(aud []string, cmp string, required bool) bool {
202200
if len(aud) == 0 {
203201
return !required
204202
}
203+
205204
// use a var here to keep constant time compare when looping over a number of claims
206205
result := false
207206

@@ -221,41 +220,39 @@ func verifyAud(aud []string, cmp string, required bool) bool {
221220
return result
222221
}
223222

224-
func verifyExp(exp *time.Time, now time.Time, required bool, skew time.Duration) bool {
225-
if exp == nil {
226-
return !required
227-
}
228-
229-
return now.Before((*exp).Add(+skew))
230-
}
231-
232-
func verifyIat(iat *time.Time, now time.Time, required bool, skew time.Duration) bool {
233-
if iat == nil {
234-
return !required
235-
}
236-
237-
t := iat.Add(-skew)
238-
return !now.Before(t)
239-
}
240-
241-
func verifyNbf(nbf *time.Time, now time.Time, required bool, skew time.Duration) bool {
242-
if nbf == nil {
243-
return !required
223+
// VerifyIssuer compares the iss claim in claims against cmp.
224+
//
225+
// If iss is not set, it will return true if the claim is not required,
226+
// otherwise false will be returned.
227+
//
228+
// Additionally, if any error occurs while retrieving the claim, e.g., when its
229+
// the wrong type, false will be returned.
230+
func (v *validator) VerifyIssuer(claims Claims, cmp string, required bool) bool {
231+
iss, err := claims.GetIssuer()
232+
if err != nil {
233+
return false
244234
}
245235

246-
t := nbf.Add(-skew)
247-
return !now.Before(t)
248-
}
249-
250-
func verifyIss(iss string, cmp string, required bool) bool {
251236
if iss == "" {
252237
return !required
253238
}
254239

255240
return iss == cmp
256241
}
257242

258-
func verifySub(sub string, cmp string, required bool) bool {
243+
// VerifySubject compares the sub claim against cmp.
244+
//
245+
// If sub is not set, it will return true if the claim is not required,
246+
// otherwise false will be returned.
247+
//
248+
// Additionally, if any error occurs while retrieving the claim, e.g., when its
249+
// the wrong type, false will be returned.
250+
func (v *validator) VerifySubject(claims Claims, cmp string, required bool) bool {
251+
sub, err := claims.GetSubject()
252+
if err != nil {
253+
return false
254+
}
255+
259256
if sub == "" {
260257
return !required
261258
}

0 commit comments

Comments
 (0)