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

Make issued at (iat) claim validation optional #175

Closed
wants to merge 14 commits into from
38 changes: 18 additions & 20 deletions claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (c RegisteredClaims) Valid(opts ...validationOption) error {
vErr.Errors |= ValidationErrorExpired
}

if !c.VerifyIssuedAt(now, false) {
if !c.VerifyIssuedAt(now, false, opts...) {
vErr.Inner = ErrTokenUsedBeforeIssued
vErr.Errors |= ValidationErrorIssuedAt
}
Expand All @@ -89,10 +89,7 @@ func (c *RegisteredClaims) VerifyAudience(cmp string, req bool) bool {
// VerifyExpiresAt compares the exp claim against cmp (cmp < exp).
// If req is false, it will return true, if exp is unset.
func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool, opts ...validationOption) bool {
validator := validator{}
for _, o := range opts {
o(&validator)
}
validator := getValidator(opts...)
if c.ExpiresAt == nil {
return verifyExp(nil, cmp, req, validator.leeway)
}
Expand All @@ -102,7 +99,12 @@ func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool, opts ...vali

// VerifyIssuedAt compares the iat claim against cmp (cmp >= iat).
// If req is false, it will return true, if iat is unset.
func (c *RegisteredClaims) VerifyIssuedAt(cmp time.Time, req bool) bool {
func (c *RegisteredClaims) VerifyIssuedAt(cmp time.Time, req bool, opts ...validationOption) bool {
validator := getValidator(opts...)
if !validator.iat {
return true
}

if c.IssuedAt == nil {
return verifyIat(nil, cmp, req)
}
Expand All @@ -113,10 +115,7 @@ func (c *RegisteredClaims) VerifyIssuedAt(cmp time.Time, req bool) bool {
// VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf).
// If req is false, it will return true, if nbf is unset.
func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool, opts ...validationOption) bool {
validator := validator{}
for _, o := range opts {
o(&validator)
}
validator := getValidator(opts...)
if c.NotBefore == nil {
return verifyNbf(nil, cmp, req, validator.leeway)
}
Expand Down Expand Up @@ -164,7 +163,7 @@ func (c StandardClaims) Valid(opts ...validationOption) error {
vErr.Errors |= ValidationErrorExpired
}

if !c.VerifyIssuedAt(now, false) {
if !c.VerifyIssuedAt(now, false, opts...) {
vErr.Inner = ErrTokenUsedBeforeIssued
vErr.Errors |= ValidationErrorIssuedAt
}
Expand All @@ -190,10 +189,7 @@ func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool {
// VerifyExpiresAt compares the exp claim against cmp (cmp < exp).
// If req is false, it will return true, if exp is unset.
func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool, opts ...validationOption) bool {
validator := validator{}
for _, o := range opts {
o(&validator)
}
validator := getValidator(opts...)
if c.ExpiresAt == 0 {
return verifyExp(nil, time.Unix(cmp, 0), req, validator.leeway)
}
Expand All @@ -204,7 +200,12 @@ func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool, opts ...validation

// VerifyIssuedAt compares the iat claim against cmp (cmp >= iat).
// If req is false, it will return true, if iat is unset.
func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool {
func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool, opts ...validationOption) bool {
validator := getValidator(opts...)
if !validator.iat {
return true
}

if c.IssuedAt == 0 {
return verifyIat(nil, time.Unix(cmp, 0), req)
}
Expand All @@ -216,10 +217,7 @@ func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool {
// VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf).
// If req is false, it will return true, if nbf is unset.
func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool, opts ...validationOption) bool {
validator := validator{}
for _, o := range opts {
o(&validator)
}
validator := getValidator(opts...)
if c.NotBefore == 0 {
return verifyNbf(nil, time.Unix(cmp, 0), req, validator.leeway)
}
Expand Down
24 changes: 14 additions & 10 deletions map_claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,7 @@ func (m MapClaims) VerifyExpiresAt(cmp int64, req bool, opts ...validationOption
return !req
}

validator := validator{}
for _, o := range opts {
o(&validator)
}
validator := getValidator(opts...)

switch exp := v.(type) {
case float64:
Expand All @@ -65,14 +62,24 @@ func (m MapClaims) VerifyExpiresAt(cmp int64, req bool, opts ...validationOption

// VerifyIssuedAt compares the exp claim against cmp (cmp >= iat).
// If req is false, it will return true, if iat is unset.
func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool {
func (m MapClaims) VerifyIssuedAt(cmp int64, req bool, opts ...validationOption) bool {
cmpTime := time.Unix(cmp, 0)

v, ok := m["iat"]
if !ok {
return !req
}

// validate the type
switch v.(type) {
case float64, json.Number:
if !getValidator(opts...).iat {
return true
}
default:
return false
}

switch iat := v.(type) {
case float64:
if iat == 0 {
Expand All @@ -99,10 +106,7 @@ func (m MapClaims) VerifyNotBefore(cmp int64, req bool, opts ...validationOption
return !req
}

validator := validator{}
for _, o := range opts {
o(&validator)
}
validator := getValidator(opts...)

switch nbf := v.(type) {
case float64:
Expand Down Expand Up @@ -141,7 +145,7 @@ func (m MapClaims) Valid(opts ...validationOption) error {
vErr.Errors |= ValidationErrorExpired
}

if !m.VerifyIssuedAt(now, false) {
if !m.VerifyIssuedAt(now, false, opts...) {
// TODO(oxisto): this should be replaced with ErrTokenUsedBeforeIssued
vErr.Inner = errors.New("Token used before issued")
vErr.Errors |= ValidationErrorIssuedAt
Expand Down
4 changes: 2 additions & 2 deletions map_claims_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,13 @@ func TestMapClaimsVerifyExpiresAtExpire(t *testing.T) {
t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got)
}

got = mapClaims.VerifyExpiresAt(exp + 1, true)
got = mapClaims.VerifyExpiresAt(exp+1, true)
if want != got {
t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got)
}

want = true
got = mapClaims.VerifyExpiresAt(exp - 1, true)
got = mapClaims.VerifyExpiresAt(exp-1, true)
if want != got {
t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got)
}
Expand Down
7 changes: 7 additions & 0 deletions parser_option.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,10 @@ func WithLeeway(d time.Duration) ParserOption {
p.validationOptions = append(p.validationOptions, withLeeway(d))
}
}

// WithIssuedAt is an option to enable the validation of the issued at (iat) claim.
func WithIssuedAt() ParserOption {
return func(p *Parser) {
p.validationOptions = append(p.validationOptions, withIssuedAt())
}
}
19 changes: 19 additions & 0 deletions validator_option.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type validationOption func(*validator)
// the API is more stable.
type validator struct {
leeway time.Duration // Leeway to provide when validating time values
iat bool
}

// withLeeway is an option to set the clock skew (leeway) window
Expand All @@ -27,3 +28,21 @@ func withLeeway(d time.Duration) validationOption {
v.leeway = d
}
}

// withIssuedAth is an option to enable the validation of the issued at (iat) claim
//
// Note that this function is (currently) un-exported, its naming is subject to change and will only be exported once
// the API is more stable.
func withIssuedAt() validationOption {
return func(v *validator) {
v.iat = true
}
}

func getValidator(opts ...validationOption) validator {
v := validator{}
for _, o := range opts {
o(&v)
}
return v
}