Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation Options - Experiment 1: Embedding validation options in claim #208

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type Claims interface {
//
// See examples for how to use this with your own claim types.
type RegisteredClaims struct {
v validator
oxisto marked this conversation as resolved.
Show resolved Hide resolved

// the `iss` (Issuer) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1
Issuer string `json:"iss,omitempty"`

Expand Down Expand Up @@ -87,10 +89,10 @@ func (c *RegisteredClaims) VerifyAudience(cmp string, req bool) bool {
// If req is false, it will return true, if exp is unset.
func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool) bool {
if c.ExpiresAt == nil {
return verifyExp(nil, cmp, req)
return verifyExp(nil, cmp, req, c.v.leeway)
}

return verifyExp(&c.ExpiresAt.Time, cmp, req)
return verifyExp(&c.ExpiresAt.Time, cmp, req, c.v.leeway)
}

// VerifyIssuedAt compares the iat claim against cmp (cmp >= iat).
Expand All @@ -107,10 +109,10 @@ func (c *RegisteredClaims) VerifyIssuedAt(cmp time.Time, req bool) bool {
// If req is false, it will return true, if nbf is unset.
func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool) bool {
if c.NotBefore == nil {
return verifyNbf(nil, cmp, req)
return verifyNbf(nil, cmp, req, c.v.leeway)
}

return verifyNbf(&c.NotBefore.Time, cmp, req)
return verifyNbf(&c.NotBefore.Time, cmp, req, c.v.leeway)
}

// VerifyIssuer compares the iss claim against cmp.
Expand All @@ -129,6 +131,8 @@ func (c *RegisteredClaims) VerifyIssuer(cmp string, req bool) bool {
//
// Deprecated: Use RegisteredClaims instead for a forward-compatible way to access registered claims in a struct.
type StandardClaims struct {
v validator
oxisto marked this conversation as resolved.
Show resolved Hide resolved

Audience string `json:"aud,omitempty"`
ExpiresAt int64 `json:"exp,omitempty"`
Id string `json:"jti,omitempty"`
Expand Down Expand Up @@ -180,11 +184,11 @@ func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool {
// If req is false, it will return true, if exp is unset.
func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool {
if c.ExpiresAt == 0 {
return verifyExp(nil, time.Unix(cmp, 0), req)
return verifyExp(nil, time.Unix(cmp, 0), req, c.v.leeway)
}

t := time.Unix(c.ExpiresAt, 0)
return verifyExp(&t, time.Unix(cmp, 0), req)
return verifyExp(&t, time.Unix(cmp, 0), req, c.v.leeway)
}

// VerifyIssuedAt compares the iat claim against cmp (cmp >= iat).
Expand All @@ -202,11 +206,11 @@ func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool {
// If req is false, it will return true, if nbf is unset.
func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool {
if c.NotBefore == 0 {
return verifyNbf(nil, time.Unix(cmp, 0), req)
return verifyNbf(nil, time.Unix(cmp, 0), req, c.v.leeway)
}

t := time.Unix(c.NotBefore, 0)
return verifyNbf(&t, time.Unix(cmp, 0), req)
return verifyNbf(&t, time.Unix(cmp, 0), req, c.v.leeway)
}

// VerifyIssuer compares the iss claim against cmp.
Expand Down Expand Up @@ -240,11 +244,12 @@ func verifyAud(aud []string, cmp string, required bool) bool {
return result
}

func verifyExp(exp *time.Time, now time.Time, required bool) bool {
func verifyExp(exp *time.Time, now time.Time, required bool, skew time.Duration) bool {
if exp == nil {
return !required
}
return now.Before(*exp)

return now.Before((*exp).Add(+skew))
}

func verifyIat(iat *time.Time, now time.Time, required bool) bool {
Expand All @@ -254,11 +259,13 @@ func verifyIat(iat *time.Time, now time.Time, required bool) bool {
return now.After(*iat) || now.Equal(*iat)
}

func verifyNbf(nbf *time.Time, now time.Time, required bool) bool {
func verifyNbf(nbf *time.Time, now time.Time, required bool, skew time.Duration) bool {
if nbf == nil {
return !required
}
return now.After(*nbf) || now.Equal(*nbf)

t := (*nbf).Add(-skew)
return now.After(t) || now.Equal(t)
}

func verifyIss(iss string, cmp string, required bool) bool {
Expand Down
12 changes: 6 additions & 6 deletions map_claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ func (m MapClaims) VerifyExpiresAt(cmp int64, req bool) bool {
switch exp := v.(type) {
case float64:
if exp == 0 {
return verifyExp(nil, cmpTime, req)
return verifyExp(nil, cmpTime, req, 0)
}

return verifyExp(&newNumericDateFromSeconds(exp).Time, cmpTime, req)
return verifyExp(&newNumericDateFromSeconds(exp).Time, cmpTime, req, 0)
case json.Number:
v, _ := exp.Float64()

return verifyExp(&newNumericDateFromSeconds(v).Time, cmpTime, req)
return verifyExp(&newNumericDateFromSeconds(v).Time, cmpTime, req, 0)
}

return false
Expand Down Expand Up @@ -97,14 +97,14 @@ func (m MapClaims) VerifyNotBefore(cmp int64, req bool) bool {
switch nbf := v.(type) {
case float64:
if nbf == 0 {
return verifyNbf(nil, cmpTime, req)
return verifyNbf(nil, cmpTime, req, 0)
}

return verifyNbf(&newNumericDateFromSeconds(nbf).Time, cmpTime, req)
return verifyNbf(&newNumericDateFromSeconds(nbf).Time, cmpTime, req, 0)
case json.Number:
v, _ := nbf.Float64()

return verifyNbf(&newNumericDateFromSeconds(v).Time, cmpTime, req)
return verifyNbf(&newNumericDateFromSeconds(v).Time, cmpTime, req, 0)
}

return false
Expand Down
14 changes: 14 additions & 0 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type Parser struct {
//
// Deprecated: In future releases, this field will not be exported anymore and should be set with an option to NewParser instead.
SkipClaimsValidation bool

v validator
oxisto marked this conversation as resolved.
Show resolved Hide resolved
}

// NewParser creates a new Parser with the specified options
Expand Down Expand Up @@ -80,6 +82,18 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf

vErr := &ValidationError{}

// Experimental: inject the validation options of the parser into the
// claims. This provides a backwards compatible way to have validation
// options, but it unfortunately only works for jwt.RegisteredClaims and
// jwt.StandardClaims
if rclaims, ok := claims.(*RegisteredClaims); ok {
rclaims.v = p.v
}

if sclaims, ok := claims.(*StandardClaims); ok {
sclaims.v = p.v
}

// Validate Claims
if !p.SkipClaimsValidation {
if err := token.Claims.Valid(); err != nil {
Expand Down
9 changes: 9 additions & 0 deletions parser_option.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package jwt

import "time"

// ParserOption is used to implement functional-style options that modify the behavior of the parser. To add
// new options, just create a function (ideally beginning with With or Without) that returns an anonymous function that
// takes a *Parser type as input and manipulates its configuration accordingly.
Expand Down Expand Up @@ -27,3 +29,10 @@ func WithoutClaimsValidation() ParserOption {
p.SkipClaimsValidation = true
}
}

// WithLeeway returns the ParserOption for specifying the leeway window.
func WithLeeway(d time.Duration) ParserOption {
return func(p *Parser) {
p.v.leeway = d
}
}
13 changes: 13 additions & 0 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,19 @@ var jwtTestData = []struct {
&jwt.Parser{UseJSONNumber: true},
jwt.SigningMethodRS256,
},
{
"RFC7519 Claims - expired by 100s with 120s skew",
"", // autogen
defaultKeyFunc,
&jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Second * 100)),
},
true,
0,
nil,
jwt.NewParser(jwt.WithLeeway(2 * time.Minute)),
jwt.SigningMethodRS256,
},
}

// signToken creates and returns a signed JWT token using signingMethod.
Expand Down
9 changes: 9 additions & 0 deletions validation_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package jwt

import "time"

// validator is magic
type validator struct {
// Leeway to provide to the current time when validating time values
leeway time.Duration
}