forked from mirror/jwt
Added timeFunc, made iat optional
This commit is contained in:
parent
1d88540186
commit
93dcd2e886
|
@ -95,7 +95,7 @@ func ExampleParseWithClaims_customClaimsType() {
|
||||||
|
|
||||||
// Example creating a token using a custom claims type and validation options. The RegisteredClaims is embedded
|
// Example creating a token using a custom claims type and validation options. The RegisteredClaims is embedded
|
||||||
// in the custom type to allow for easy encoding, parsing and validation of standard claims.
|
// in the custom type to allow for easy encoding, parsing and validation of standard claims.
|
||||||
func ExampleParseWithClaims_customValidator() {
|
func ExampleParseWithClaims_validationOptions() {
|
||||||
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiYXVkIjoic2luZ2xlIn0.QAWg1vGvnqRuCFTMcPkjZljXHh8U3L_qUjszOtQbeaA"
|
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiYXVkIjoic2luZ2xlIn0.QAWg1vGvnqRuCFTMcPkjZljXHh8U3L_qUjszOtQbeaA"
|
||||||
|
|
||||||
type MyCustomClaims struct {
|
type MyCustomClaims struct {
|
||||||
|
@ -117,7 +117,41 @@ func ExampleParseWithClaims_customValidator() {
|
||||||
// Output: bar test
|
// Output: bar test
|
||||||
}
|
}
|
||||||
|
|
||||||
// An example of parsing the error types using bitfield checks
|
type MyCustomClaims struct {
|
||||||
|
Foo string `json:"foo"`
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MyCustomClaims) CustomValidation() error {
|
||||||
|
if m.Foo != "bar" {
|
||||||
|
return errors.New("must be foobar")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example creating a token using a custom claims type and validation options.
|
||||||
|
// The RegisteredClaims is embedded in the custom type to allow for easy
|
||||||
|
// encoding, parsing and validation of standard claims and the function
|
||||||
|
// CustomValidation is implemented.
|
||||||
|
func ExampleParseWithClaims_customValidation() {
|
||||||
|
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiYXVkIjoic2luZ2xlIn0.QAWg1vGvnqRuCFTMcPkjZljXHh8U3L_qUjszOtQbeaA"
|
||||||
|
|
||||||
|
validator := jwt.NewValidator(jwt.WithLeeway(5 * time.Second))
|
||||||
|
token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return []byte("AllYourBase"), nil
|
||||||
|
}, jwt.WithValidator(validator))
|
||||||
|
|
||||||
|
if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid {
|
||||||
|
fmt.Printf("%v %v", claims.Foo, claims.RegisteredClaims.Issuer)
|
||||||
|
} else {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output: bar test
|
||||||
|
}
|
||||||
|
|
||||||
|
// An example of parsing the error types using errors.Is.
|
||||||
func ExampleParse_errorChecking() {
|
func ExampleParse_errorChecking() {
|
||||||
// Token from another example. This token is expired
|
// Token from another example. This token is expired
|
||||||
var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
|
var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
|
||||||
|
|
12
parser.go
12
parser.go
|
@ -7,6 +7,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DefaultValidator is the default validator that is used, if no custom validator is supplied in a Parser.
|
||||||
|
var DefaultValidator = NewValidator()
|
||||||
|
|
||||||
type Parser struct {
|
type Parser struct {
|
||||||
// If populated, only these methods will be considered valid.
|
// If populated, only these methods will be considered valid.
|
||||||
//
|
//
|
||||||
|
@ -28,12 +31,9 @@ type Parser struct {
|
||||||
|
|
||||||
// NewParser creates a new Parser with the specified options
|
// NewParser creates a new Parser with the specified options
|
||||||
func NewParser(options ...ParserOption) *Parser {
|
func NewParser(options ...ParserOption) *Parser {
|
||||||
p := &Parser{
|
p := &Parser{}
|
||||||
// Supply a default validator
|
|
||||||
validator: NewValidator(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop through our parsing options and apply them
|
// Loop through our parsing options and apply them
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(p)
|
option(p)
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
|
||||||
if !p.SkipClaimsValidation {
|
if !p.SkipClaimsValidation {
|
||||||
// Make sure we have at least a default validator
|
// Make sure we have at least a default validator
|
||||||
if p.validator == nil {
|
if p.validator == nil {
|
||||||
p.validator = NewValidator()
|
p.validator = DefaultValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.validator.Validate(claims); err != nil {
|
if err := p.validator.Validate(claims); err != nil {
|
||||||
|
|
6
token.go
6
token.go
|
@ -4,7 +4,6 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DecodePaddingAllowed will switch the codec used for decoding JWTs respectively. Note that the JWS RFC7515
|
// DecodePaddingAllowed will switch the codec used for decoding JWTs respectively. Note that the JWS RFC7515
|
||||||
|
@ -14,11 +13,6 @@ import (
|
||||||
// To use the non-recommended decoding, set this boolean to `true` prior to using this package.
|
// To use the non-recommended decoding, set this boolean to `true` prior to using this package.
|
||||||
var DecodePaddingAllowed bool
|
var DecodePaddingAllowed bool
|
||||||
|
|
||||||
// TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time).
|
|
||||||
// You can override it to use another time value. This is useful for testing or if your
|
|
||||||
// server uses a different time zone than your tokens.
|
|
||||||
var TimeFunc = time.Now
|
|
||||||
|
|
||||||
// Keyfunc will be used by the Parse methods as a callback function to supply
|
// Keyfunc will be used by the Parse methods as a callback function to supply
|
||||||
// the key for verification. The function receives the parsed,
|
// the key for verification. The function receives the parsed,
|
||||||
// but unverified Token. This allows you to use properties in the
|
// but unverified Token. This allows you to use properties in the
|
||||||
|
|
60
validator.go
60
validator.go
|
@ -6,13 +6,48 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Validator is the core of the new Validation API. It is
|
||||||
type Validator struct {
|
type Validator struct {
|
||||||
|
// leeway is an optional leeway that can be provided to account for clock skew.
|
||||||
leeway time.Duration
|
leeway time.Duration
|
||||||
|
|
||||||
|
// timeFunc is used to supply the current time that is needed for
|
||||||
|
// validation. If unspecified, this defaults to time.Now.
|
||||||
|
timeFunc func() time.Time
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// necessary. However, if wanted, it can be checked if the iat is
|
||||||
|
// unrealistic, i.e., in the future.
|
||||||
|
verifyIat bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type customValidationType interface {
|
||||||
|
CustomValidation() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewValidator(opts ...ValidatorOption) *Validator {
|
||||||
|
v := &Validator{}
|
||||||
|
|
||||||
|
// Apply the validator options
|
||||||
|
for _, o := range opts {
|
||||||
|
o(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) Validate(claims Claims) error {
|
func (v *Validator) Validate(claims Claims) error {
|
||||||
|
var now time.Time
|
||||||
vErr := new(ValidationError)
|
vErr := new(ValidationError)
|
||||||
now := TimeFunc()
|
|
||||||
|
// Check, if we have a time func
|
||||||
|
if v.timeFunc != nil {
|
||||||
|
now = v.timeFunc()
|
||||||
|
} else {
|
||||||
|
now = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
if !v.VerifyExpiresAt(claims, now, false) {
|
if !v.VerifyExpiresAt(claims, now, false) {
|
||||||
exp := claims.GetExpirationTime()
|
exp := claims.GetExpirationTime()
|
||||||
|
@ -21,7 +56,8 @@ func (v *Validator) Validate(claims Claims) error {
|
||||||
vErr.Errors |= ValidationErrorExpired
|
vErr.Errors |= ValidationErrorExpired
|
||||||
}
|
}
|
||||||
|
|
||||||
if !v.VerifyIssuedAt(claims, now, false) {
|
// Check iat if the option is enabled
|
||||||
|
if v.verifyIat && !v.VerifyIssuedAt(claims, now, false) {
|
||||||
vErr.Inner = ErrTokenUsedBeforeIssued
|
vErr.Inner = ErrTokenUsedBeforeIssued
|
||||||
vErr.Errors |= ValidationErrorIssuedAt
|
vErr.Errors |= ValidationErrorIssuedAt
|
||||||
}
|
}
|
||||||
|
@ -31,6 +67,16 @@ func (v *Validator) Validate(claims Claims) error {
|
||||||
vErr.Errors |= ValidationErrorNotValidYet
|
vErr.Errors |= ValidationErrorNotValidYet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finally, we want to give the claim itself some possibility to do some
|
||||||
|
// additional custom validation based on their custom claims
|
||||||
|
cvt, ok := claims.(customValidationType)
|
||||||
|
if ok {
|
||||||
|
if err := cvt.CustomValidation(); err != nil {
|
||||||
|
vErr.Inner = err
|
||||||
|
vErr.Errors |= ValidationErrorClaimsInvalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if vErr.valid() {
|
if vErr.valid() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -83,16 +129,6 @@ func (v *Validator) VerifyIssuer(claims Claims, cmp string, req bool) bool {
|
||||||
return verifyIss(claims.GetIssuer(), cmp, req)
|
return verifyIss(claims.GetIssuer(), cmp, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewValidator(opts ...ValidatorOption) *Validator {
|
|
||||||
v := &Validator{}
|
|
||||||
|
|
||||||
for _, o := range opts {
|
|
||||||
o(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- helpers
|
// ----- helpers
|
||||||
|
|
||||||
func verifyAud(aud []string, cmp string, required bool) bool {
|
func verifyAud(aud []string, cmp string, required bool) bool {
|
||||||
|
|
|
@ -9,9 +9,26 @@ import "time"
|
||||||
// accordingly.
|
// accordingly.
|
||||||
type ValidatorOption func(*Validator)
|
type ValidatorOption func(*Validator)
|
||||||
|
|
||||||
// WithLeeway returns the ParserOption for specifying the leeway window.
|
// WithLeeway returns the ValidatorOption for specifying the leeway window.
|
||||||
func WithLeeway(leeway time.Duration) ValidatorOption {
|
func WithLeeway(leeway time.Duration) ValidatorOption {
|
||||||
return func(v *Validator) {
|
return func(v *Validator) {
|
||||||
v.leeway = leeway
|
v.leeway = leeway
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithTimeFunc returns the ValidatorOption for specifying the time func. The
|
||||||
|
// primary use-case for this is testing. If you are looking for a way to account
|
||||||
|
// for clock-skew, WithLeeway should be used instead.
|
||||||
|
func WithTimeFunc(f func() time.Time) ValidatorOption {
|
||||||
|
return func(v *Validator) {
|
||||||
|
v.timeFunc = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithIssuedAtVerification returns the ValidatorOption to enable verification
|
||||||
|
// of issued-at.
|
||||||
|
func WithIssuedAtVerification() ValidatorOption {
|
||||||
|
return func(v *Validator) {
|
||||||
|
v.verifyIat = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue