Skip to content

Commit 93dcd2e

Browse files
committed
Added timeFunc, made iat optional
1 parent 1d88540 commit 93dcd2e

File tree

5 files changed

+108
-27
lines changed

5 files changed

+108
-27
lines changed

example_test.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func ExampleParseWithClaims_customClaimsType() {
9595

9696
// Example creating a token using a custom claims type and validation options. The RegisteredClaims is embedded
9797
// in the custom type to allow for easy encoding, parsing and validation of standard claims.
98-
func ExampleParseWithClaims_customValidator() {
98+
func ExampleParseWithClaims_validationOptions() {
9999
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiYXVkIjoic2luZ2xlIn0.QAWg1vGvnqRuCFTMcPkjZljXHh8U3L_qUjszOtQbeaA"
100100

101101
type MyCustomClaims struct {
@@ -117,7 +117,41 @@ func ExampleParseWithClaims_customValidator() {
117117
// Output: bar test
118118
}
119119

120-
// An example of parsing the error types using bitfield checks
120+
type MyCustomClaims struct {
121+
Foo string `json:"foo"`
122+
jwt.RegisteredClaims
123+
}
124+
125+
func (m MyCustomClaims) CustomValidation() error {
126+
if m.Foo != "bar" {
127+
return errors.New("must be foobar")
128+
}
129+
130+
return nil
131+
}
132+
133+
// Example creating a token using a custom claims type and validation options.
134+
// The RegisteredClaims is embedded in the custom type to allow for easy
135+
// encoding, parsing and validation of standard claims and the function
136+
// CustomValidation is implemented.
137+
func ExampleParseWithClaims_customValidation() {
138+
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiYXVkIjoic2luZ2xlIn0.QAWg1vGvnqRuCFTMcPkjZljXHh8U3L_qUjszOtQbeaA"
139+
140+
validator := jwt.NewValidator(jwt.WithLeeway(5 * time.Second))
141+
token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
142+
return []byte("AllYourBase"), nil
143+
}, jwt.WithValidator(validator))
144+
145+
if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid {
146+
fmt.Printf("%v %v", claims.Foo, claims.RegisteredClaims.Issuer)
147+
} else {
148+
fmt.Println(err)
149+
}
150+
151+
// Output: bar test
152+
}
153+
154+
// An example of parsing the error types using errors.Is.
121155
func ExampleParse_errorChecking() {
122156
// Token from another example. This token is expired
123157
var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"

parser.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import (
77
"strings"
88
)
99

10+
// DefaultValidator is the default validator that is used, if no custom validator is supplied in a Parser.
11+
var DefaultValidator = NewValidator()
12+
1013
type Parser struct {
1114
// If populated, only these methods will be considered valid.
1215
//
@@ -28,12 +31,9 @@ type Parser struct {
2831

2932
// NewParser creates a new Parser with the specified options
3033
func NewParser(options ...ParserOption) *Parser {
31-
p := &Parser{
32-
// Supply a default validator
33-
validator: NewValidator(),
34-
}
34+
p := &Parser{}
3535

36-
// loop through our parsing options and apply them
36+
// Loop through our parsing options and apply them
3737
for _, option := range options {
3838
option(p)
3939
}
@@ -89,7 +89,7 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
8989
if !p.SkipClaimsValidation {
9090
// Make sure we have at least a default validator
9191
if p.validator == nil {
92-
p.validator = NewValidator()
92+
p.validator = DefaultValidator
9393
}
9494

9595
if err := p.validator.Validate(claims); err != nil {

token.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"encoding/base64"
55
"encoding/json"
66
"strings"
7-
"time"
87
)
98

109
// DecodePaddingAllowed will switch the codec used for decoding JWTs respectively. Note that the JWS RFC7515
@@ -14,11 +13,6 @@ import (
1413
// To use the non-recommended decoding, set this boolean to `true` prior to using this package.
1514
var DecodePaddingAllowed bool
1615

17-
// TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time).
18-
// You can override it to use another time value. This is useful for testing or if your
19-
// server uses a different time zone than your tokens.
20-
var TimeFunc = time.Now
21-
2216
// Keyfunc will be used by the Parse methods as a callback function to supply
2317
// the key for verification. The function receives the parsed,
2418
// but unverified Token. This allows you to use properties in the

validator.go

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,48 @@ import (
66
"time"
77
)
88

9+
// Validator is the core of the new Validation API. It is
910
type Validator struct {
11+
// leeway is an optional leeway that can be provided to account for clock skew.
1012
leeway time.Duration
13+
14+
// timeFunc is used to supply the current time that is needed for
15+
// validation. If unspecified, this defaults to time.Now.
16+
timeFunc func() time.Time
17+
18+
// verifyIat specifies whether the iat (Issued At) claim will be verified.
19+
// According to https://www.rfc-editor.org/rfc/rfc7519#section-4.1.6 this
20+
// only specifies the age of the token, but no validation check is
21+
// necessary. However, if wanted, it can be checked if the iat is
22+
// unrealistic, i.e., in the future.
23+
verifyIat bool
24+
}
25+
26+
type customValidationType interface {
27+
CustomValidation() error
28+
}
29+
30+
func NewValidator(opts ...ValidatorOption) *Validator {
31+
v := &Validator{}
32+
33+
// Apply the validator options
34+
for _, o := range opts {
35+
o(v)
36+
}
37+
38+
return v
1139
}
1240

1341
func (v *Validator) Validate(claims Claims) error {
42+
var now time.Time
1443
vErr := new(ValidationError)
15-
now := TimeFunc()
44+
45+
// Check, if we have a time func
46+
if v.timeFunc != nil {
47+
now = v.timeFunc()
48+
} else {
49+
now = time.Now()
50+
}
1651

1752
if !v.VerifyExpiresAt(claims, now, false) {
1853
exp := claims.GetExpirationTime()
@@ -21,7 +56,8 @@ func (v *Validator) Validate(claims Claims) error {
2156
vErr.Errors |= ValidationErrorExpired
2257
}
2358

24-
if !v.VerifyIssuedAt(claims, now, false) {
59+
// Check iat if the option is enabled
60+
if v.verifyIat && !v.VerifyIssuedAt(claims, now, false) {
2561
vErr.Inner = ErrTokenUsedBeforeIssued
2662
vErr.Errors |= ValidationErrorIssuedAt
2763
}
@@ -31,6 +67,16 @@ func (v *Validator) Validate(claims Claims) error {
3167
vErr.Errors |= ValidationErrorNotValidYet
3268
}
3369

70+
// Finally, we want to give the claim itself some possibility to do some
71+
// additional custom validation based on their custom claims
72+
cvt, ok := claims.(customValidationType)
73+
if ok {
74+
if err := cvt.CustomValidation(); err != nil {
75+
vErr.Inner = err
76+
vErr.Errors |= ValidationErrorClaimsInvalid
77+
}
78+
}
79+
3480
if vErr.valid() {
3581
return nil
3682
}
@@ -83,16 +129,6 @@ func (v *Validator) VerifyIssuer(claims Claims, cmp string, req bool) bool {
83129
return verifyIss(claims.GetIssuer(), cmp, req)
84130
}
85131

86-
func NewValidator(opts ...ValidatorOption) *Validator {
87-
v := &Validator{}
88-
89-
for _, o := range opts {
90-
o(v)
91-
}
92-
93-
return v
94-
}
95-
96132
// ----- helpers
97133

98134
func verifyAud(aud []string, cmp string, required bool) bool {

validator_option.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,26 @@ import "time"
99
// accordingly.
1010
type ValidatorOption func(*Validator)
1111

12-
// WithLeeway returns the ParserOption for specifying the leeway window.
12+
// WithLeeway returns the ValidatorOption for specifying the leeway window.
1313
func WithLeeway(leeway time.Duration) ValidatorOption {
1414
return func(v *Validator) {
1515
v.leeway = leeway
1616
}
1717
}
18+
19+
// WithTimeFunc returns the ValidatorOption for specifying the time func. The
20+
// primary use-case for this is testing. If you are looking for a way to account
21+
// for clock-skew, WithLeeway should be used instead.
22+
func WithTimeFunc(f func() time.Time) ValidatorOption {
23+
return func(v *Validator) {
24+
v.timeFunc = f
25+
}
26+
}
27+
28+
// WithIssuedAtVerification returns the ValidatorOption to enable verification
29+
// of issued-at.
30+
func WithIssuedAtVerification() ValidatorOption {
31+
return func(v *Validator) {
32+
v.verifyIat = true
33+
}
34+
}

0 commit comments

Comments
 (0)