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

Allow leeway when validating iat and nbf claims #12

Closed
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
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