Skip to content

Commit

Permalink
Allow resetting the default jwt validators (#1101)
Browse files Browse the repository at this point in the history
* Allow resetting the default jwt validators

* slightly augment docs

* add explicit warning in docs
  • Loading branch information
lestrrat authored Mar 11, 2024
1 parent 34a5966 commit 8dd780f
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 19 deletions.
22 changes: 16 additions & 6 deletions Changes
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ v1 and v2, please read the Changes-v2.md file (https://github.com/lestrrat-go/jw

v2.0.22 UNRELEASED
[New Features]
* [jwt] Added jwt.ParseCookie() function
* [jwt] jwt.ParseRequest() can now accept a new option, jwt.WithCookieKey() to
* [jwt] Added `jwt.ParseCookie()` function
* [jwt] `jwt.ParseRequest()` can now accept a new option, jwt.WithCookieKey() to
specify a cookie name to extract the token from.
* [jwt] jwt.ParseRequest() and jwt.ParseCookie can accept the jwt.WithCookie() option,
* [jwt] `jwt.ParseRequest()` and `jwt.ParseCookie()` can accept the `jwt.WithCookie()` option,
which will, upon successful token parsing, make the functions assign the *http.Cookie
used to parse the token. This allows users to further inspect the cookie where the
token came from, should the need arise.
* [jwt] jwt.ParseRequest() no longer automatically looks for "Authorization" header when
only jwt.WithFormKey() is used. This behavior is the same for jwt.WithCookieKey() and
* [jwt] `jwt.ParseRequest()` no longer automatically looks for "Authorization" header when
only `jwt.WithFormKey()` is used. This behavior is the same for `jwt.WithCookieKey()` and
any similar options that may be implemented in the future.

# previously
Expand All @@ -26,7 +26,17 @@ v2.0.22 UNRELEASED
jwt.ParseRequest(req) // same as before
jwt.ParseRequest(req, jwt.WithFormKey("foo")) // looks under foo
jwt.ParseReuqest(req, jwt.WithHeaderKey("Authorization"), jwt.WithFormKey("foo")) // looks under foo AND Authorization


* [jwt] Add `jwt.WithResetValidators()` option to `jwt.Validate()`. This option
will allow you to tell `jwt.Validate()` to NOT automatically check the
default validators (`iat`, `exp`, and `nbf`), so that you can completely customize
the validation with the validators you specify using `jwt.WithValidator()`.

This sort of behavior is useful for special cases such as
https://openid.net/specs/openid-connect-rpinitiated-1_0.html. However, you SHOULD NOT
use this option unless you know exactly what you are doing, as this will pose
significant security issues when used incorrectly.

v2.0.21 07 Mar 2024
[Security]
* [jwe] Added `jwe.Settings(jwe.WithMaxDecompressBufferSize(int64))` to specify the
Expand Down
32 changes: 23 additions & 9 deletions jwt/jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,17 +466,31 @@ func TestValidateClaims(t *testing.T) {
t.Parallel()
// GitHub issue #37: tokens are invalid in the second they are created (because Now() is not after IssuedAt())
t.Run("Empty fields", func(t *testing.T) {
t.Parallel()
token := jwt.New()
require.Error(t, jwt.Validate(token, jwt.WithIssuer("foo")), `token.Validate should fail`)
require.Error(t, jwt.Validate(token, jwt.WithJwtID("foo")), `token.Validate should fail`)
require.Error(t, jwt.Validate(token, jwt.WithSubject("foo")), `token.Validate should fail`)
})
t.Run("Reset Validator, No validator", func(t *testing.T) {
t.Parallel()
token := jwt.New()
now := time.Now().UTC()
token.Set(jwt.IssuedAtKey, now)

if !assert.Error(t, jwt.Validate(token, jwt.WithIssuer("foo")), `token.Validate should fail`) {
return
}
if !assert.Error(t, jwt.Validate(token, jwt.WithJwtID("foo")), `token.Validate should fail`) {
return
}
if !assert.Error(t, jwt.Validate(token, jwt.WithSubject("foo")), `token.Validate should fail`) {
return
}
err := jwt.Validate(token, jwt.WithResetValidators(true))
require.Error(t, err, `token.Validate should fail`)
require.Contains(t, err.Error(), "no validators specified", `error message should contain "no validators specified"`)
})
t.Run("Reset Validator, Check iss only", func(t *testing.T) {
t.Parallel()
token := jwt.New()
iat := time.Now().UTC().Add(time.Hour * 24)
token.Set(jwt.IssuedAtKey, iat)
token.Set(jwt.IssuerKey, "github.com/lestrrat-go")

err := jwt.Validate(token, jwt.WithResetValidators(true), jwt.WithIssuer("github.com/lestrrat-go"))
require.NoError(t, err, `token.Validate should succeed`)
})
t.Run(jwt.IssuedAtKey+"+skew", func(t *testing.T) {
t.Parallel()
Expand Down
22 changes: 22 additions & 0 deletions jwt/options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,28 @@ options:
Please be aware that in the next major release of this library,
`jwt.Validate()`'s signature will change to include an explicit
`context.Context` object.
- ident: ResetValidators
interface: ValidateOption
argument_type: bool
comment: |
WithResetValidators specifies that the default validators should be
reset before applying the custom validators. By default `jwt.Validate()`
checks for the validity of JWT by checking `exp`, `nbf`, and `iat`, even
when you specify more validators through other options.
You SHOULD NOT use this option unless you know exactly what you are doing,
as this will pose significant security issues when used incorrectly.
Using this option with the value `true` will remove all default checks,
and will expect you to specify validators as options. This is useful when you
want to skip the default validators and only use specific validators, such as
for https://openid.net/specs/openid-connect-rpinitiated-1_0.html, where
the token could be accepted even if the token is expired.
If you set this option to true and you do not specify any validators,
`jwt.Validate()` will return an error.
The default value is `false` (`iat`, `exp`, and `nbf` are automatically checked).
- ident: FlattenAudience
interface: GlobalOption
argument_type: bool
Expand Down
27 changes: 27 additions & 0 deletions jwt/options_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions jwt/options_gen_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 19 additions & 4 deletions jwt/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ func Validate(t Token, options ...ValidateOption) error {

var clock Clock = ClockFunc(time.Now)
var skew time.Duration
var validators = []Validator{
var baseValidators = []Validator{
IsIssuedAtValid(),
IsExpirationValid(),
IsNbfValid(),
}
var extraValidators []Validator
var resetValidators bool
for _, o := range options {
//nolint:forcetypeassert
switch o.Ident() {
Expand All @@ -64,6 +66,8 @@ func Validate(t Token, options ...ValidateOption) error {
trunc = o.Value().(time.Duration)
case identContext{}:
ctx = o.Value().(context.Context)
case identResetValidators{}:
resetValidators = o.Value().(bool)
case identValidator{}:
v := o.Value().(Validator)
switch v := v.(type) {
Expand All @@ -72,22 +76,33 @@ func Validate(t Token, options ...ValidateOption) error {
if err := isSupportedTimeClaim(v.c1); err != nil {
return err
}
validators = append(validators, IsRequired(v.c1))
extraValidators = append(extraValidators, IsRequired(v.c1))
}
if v.c2 != "" {
if err := isSupportedTimeClaim(v.c2); err != nil {
return err
}
validators = append(validators, IsRequired(v.c2))
extraValidators = append(extraValidators, IsRequired(v.c2))
}
}
validators = append(validators, v)
extraValidators = append(extraValidators, v)
}
}

ctx = SetValidationCtxSkew(ctx, skew)
ctx = SetValidationCtxClock(ctx, clock)
ctx = SetValidationCtxTruncation(ctx, trunc)

var validators []Validator
if !resetValidators {
validators = append(baseValidators, extraValidators...)
} else {
if len(extraValidators) == 0 {
return fmt.Errorf(`no validators specified: jwt.WithResetValidators(true) and no jwt.WithValidator() specified`)
}
validators = extraValidators
}

for _, v := range validators {
if err := v.Validate(ctx, t); err != nil {
return err
Expand Down

0 comments on commit 8dd780f

Please sign in to comment.