Skip to content
This repository has been archived by the owner on Aug 10, 2021. It is now read-only.

Allow leeway when validating iat and nbf claims #12

Closed
42 changes: 28 additions & 14 deletions claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,37 @@ import (
"time"
)

// For a type to be a Claims object, it must just have a Valid method that determines
// if the token is invalid for any supported reason
// Claims must support the following.
//
// - Valid determines if token is invalid for any supported reason.
type Claims interface {
Valid() error
}

// Leeway must support the following.
//
// - Allow set allowed leeway when validating iat and/or nbf claim.
type Leeway interface {
Allow(n time.Duration) Claims
}

// Structured version of Claims Section, as referenced at
// https://tools.ietf.org/html/rfc7519#section-4.1
// See examples for how to use this with your own claim types
type StandardClaims struct {
Audience []string `json:"aud,omitempty"`
ExpiresAt int64 `json:"exp,omitempty"`
Id string `json:"jti,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
Issuer string `json:"iss,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Subject string `json:"sub,omitempty"`
ExpiresAt int64 `json:"exp,omitempty"`
Id string `json:"jti,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
Issuer string `json:"iss,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Subject string `json:"sub,omitempty"`
leeway int64
}

// Validates time based claims "exp, iat, nbf".
// There is no accounting for clock skew.
// As well, if any of the above claims are not in the token, it will still
// be considered a valid claim.
// Validates time based claims "exp, iat, nbf", as well, if any of the claims
// are not in the token, it will still be considered a valid claim. Leeway can
// be applied when optional through Leeway.Allow() through calling routine.
func (c StandardClaims) Valid() error {
vErr := new(ValidationError)
now := TimeFunc().Unix()
Expand Down Expand Up @@ -58,6 +66,12 @@ func (c StandardClaims) Valid() error {
return vErr
}

// Allow sets allowed leeway when validating claims nbf and iat.
func (c StandardClaims) Allow(n time.Duration) Claims {
c.leeway = int64(n.Seconds())
return c
}

// Compares the aud claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool {
Expand All @@ -73,7 +87,7 @@ func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool {
// Compares the iat claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool {
return verifyIat(c.IssuedAt, cmp, req)
return verifyIat(c.IssuedAt-c.leeway, cmp, req)
}

// Compares the iss claim against cmp.
Expand All @@ -85,7 +99,7 @@ func (c *StandardClaims) VerifyIssuer(cmp string, req bool) bool {
// Compares the nbf claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool {
return verifyNbf(c.NotBefore, cmp, req)
return verifyNbf(c.NotBefore-c.leeway, cmp, req)
}

// ----- helpers
Expand Down
2 changes: 1 addition & 1 deletion hmac_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func ExampleParse_hmac() {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}

// hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
return hmacSampleSecret, nil
})
Expand Down
3 changes: 1 addition & 2 deletions map_claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ package jwt
import (
"encoding/json"
"errors"
// "fmt"
)

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

// Compares the aud claim against cmp.
Expand Down
20 changes: 12 additions & 8 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import (
"encoding/json"
"fmt"
"strings"
"time"
)

type Parser struct {
ValidMethods []string // If populated, only these methods will be considered valid
UseJSONNumber bool // Use JSON Number format in JSON decoder
SkipClaimsValidation bool // Skip claims validation during token parsing
ValidMethods []string // If populated, only these methods will be considered valid
UseJSONNumber bool // Use JSON Number format in JSON decoder
SkipClaimsValidation bool // Skip claims validation during token parsing
Leeway time.Duration // Allowed leeway when validating iat, nbf claims.
}

// Parse, validate, and return a token.
Expand Down Expand Up @@ -55,13 +57,15 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
}
return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}
}

vErr := &ValidationError{}

// Validate Claims
// Validate Claims.
if !p.SkipClaimsValidation {
if err := token.Claims.Valid(); err != nil {

if t, ok := token.Claims.(Leeway); ok {
err = t.Allow(p.Leeway).Valid()
} else {
err = token.Claims.Valid()
}
if err != nil {
// If the Claims Valid returned an error, check if it is a validation error,
// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
if e, ok := err.(*ValidationError); !ok {
Expand Down
32 changes: 32 additions & 0 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,38 @@ var jwtTestData = []struct {
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
&jwt.Parser{UseJSONNumber: true},
},
{
"Validate iat and nbf with sufficient leeway",
"", // autogen
defaultKeyFunc,
&jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Minute * 5).Unix(),
IssuedAt: time.Now().Add(time.Minute * 4).Unix(),
NotBefore: time.Now().Add(time.Minute * 4).Unix(),
},
true,
0,
&jwt.Parser{
UseJSONNumber: true,
Leeway: 5 * time.Minute,
},
},
{
"Validate iat and nbf with insufficient leeway",
"", // autogen
defaultKeyFunc,
&jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Minute * 5).Unix(),
IssuedAt: time.Now().Add(time.Minute * 4).Unix(),
NotBefore: time.Now().Add(time.Minute * 4).Unix(),
},
false,
0,
&jwt.Parser{
UseJSONNumber: true,
Leeway: 1 * time.Minute,
},
},
{
"SkipClaimsValidation during token parsing",
"", // autogen
Expand Down