From 5c8660e092e87c6452f544438ef50208e67d2a7b Mon Sep 17 00:00:00 2001 From: Tareq Sharafy Date: Thu, 28 Sep 2023 03:26:25 +0300 Subject: [PATCH 1/3] feat: allow making exp claim required --- map_claims_test.go | 9 +++++++++ parser_option.go | 8 ++++++++ validator.go | 5 ++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/map_claims_test.go b/map_claims_test.go index 8cd33db3..861dba50 100644 --- a/map_claims_test.go +++ b/map_claims_test.go @@ -106,6 +106,15 @@ func TestMapclaimsVerifyExpiresAtInvalidTypeString(t *testing.T) { } } +func TestMapclaimsVerifyExpiresRequiredButMissing(t *testing.T) { + mapClaims := MapClaims{} + want := false + got := newValidator(WithRequiredExpiration()).Validate(mapClaims) + + if want != (got == nil) { + t.Fatalf("Failed to verify claims, wanted: %v got %v", want, (got == nil)) + } +} func TestMapClaimsVerifyExpiresAtExpire(t *testing.T) { exp := time.Now() mapClaims := MapClaims{ diff --git a/parser_option.go b/parser_option.go index 1b5af970..68e3b8b5 100644 --- a/parser_option.go +++ b/parser_option.go @@ -58,6 +58,14 @@ func WithIssuedAt() ParserOption { } } +// WithRequiredExpiration returns the ParserOption to make exp claim required. +// By default exp claim is optional. +func WithRequiredExpiration() ParserOption { + return func(p *Parser) { + p.validator.requireExp = true + } +} + // WithAudience configures the validator to require the specified audience in // the `aud` claim. Validation will fail if the audience is not listed in the // token or the `aud` claim is missing. diff --git a/validator.go b/validator.go index 38504389..79243803 100644 --- a/validator.go +++ b/validator.go @@ -42,6 +42,9 @@ type validator struct { // validation. If unspecified, this defaults to time.Now. timeFunc func() time.Time + // requireExp specifies whether the exp claim is required + requireExp bool + // verifyIat specifies whether the iat (Issued At) claim will be verified. // According to https://www.rfc-editor.org/rfc/rfc7519#section-4.1.6 this // only specifies the age of the token, but no validation check is @@ -87,7 +90,7 @@ func (v *validator) Validate(claims Claims) error { // We always need to check the expiration time, but usage of the claim // itself is OPTIONAL. - if err = v.verifyExpiresAt(claims, now, false); err != nil { + if err = v.verifyExpiresAt(claims, now, v.requireExp); err != nil { errs = append(errs, err) } From 2e34fb69235c7197a4c55aa2755ee9fc7ba77f18 Mon Sep 17 00:00:00 2001 From: Tareq Sharafy Date: Mon, 2 Oct 2023 11:02:47 +0300 Subject: [PATCH 2/3] move test --- map_claims_test.go | 9 --------- parser_option.go | 4 ++-- parser_test.go | 10 ++++++++++ 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/map_claims_test.go b/map_claims_test.go index 861dba50..8cd33db3 100644 --- a/map_claims_test.go +++ b/map_claims_test.go @@ -106,15 +106,6 @@ func TestMapclaimsVerifyExpiresAtInvalidTypeString(t *testing.T) { } } -func TestMapclaimsVerifyExpiresRequiredButMissing(t *testing.T) { - mapClaims := MapClaims{} - want := false - got := newValidator(WithRequiredExpiration()).Validate(mapClaims) - - if want != (got == nil) { - t.Fatalf("Failed to verify claims, wanted: %v got %v", want, (got == nil)) - } -} func TestMapClaimsVerifyExpiresAtExpire(t *testing.T) { exp := time.Now() mapClaims := MapClaims{ diff --git a/parser_option.go b/parser_option.go index 68e3b8b5..88a780fb 100644 --- a/parser_option.go +++ b/parser_option.go @@ -58,9 +58,9 @@ func WithIssuedAt() ParserOption { } } -// WithRequiredExpiration returns the ParserOption to make exp claim required. +// WithExpirationRequired returns the ParserOption to make exp claim required. // By default exp claim is optional. -func WithRequiredExpiration() ParserOption { +func WithExpirationRequired() ParserOption { return func(p *Parser) { p.validator.requireExp = true } diff --git a/parser_test.go b/parser_test.go index 1825dfc3..c0f81711 100644 --- a/parser_test.go +++ b/parser_test.go @@ -423,6 +423,16 @@ var jwtTestData = []struct { jwt.NewParser(jwt.WithLeeway(2 * time.Minute)), jwt.SigningMethodRS256, }, + { + "rejects if exp is required but missing", + "", // autogen + defaultKeyFunc, + &jwt.RegisteredClaims{}, + false, + []error{jwt.ErrTokenInvalidClaims}, + jwt.NewParser(jwt.WithExpirationRequired()), + jwt.SigningMethodRS256, + }, } // signToken creates and returns a signed JWT token using signingMethod. From 2355e74197e1fb8b7c21d9ef719fafbc95a89acb Mon Sep 17 00:00:00 2001 From: Tarek Sharafi Date: Mon, 2 Oct 2023 12:54:46 +0300 Subject: [PATCH 3/3] update doc --- validator.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/validator.go b/validator.go index 79243803..3082c8c7 100644 --- a/validator.go +++ b/validator.go @@ -89,7 +89,8 @@ func (v *validator) Validate(claims Claims) error { } // We always need to check the expiration time, but usage of the claim - // itself is OPTIONAL. + // itself is OPTIONAL by default. requireExp overrides this behavior + // and makes the exp claim mandatory. if err = v.verifyExpiresAt(claims, now, v.requireExp); err != nil { errs = append(errs, err) }