forked from mirror/jwt
Added more options to check iss and sub
This commit is contained in:
parent
5d57c292ea
commit
5a65c47732
|
@ -11,5 +11,6 @@ type Claims interface {
|
|||
GetIssuedAt() (*NumericDate, error)
|
||||
GetNotBefore() (*NumericDate, error)
|
||||
GetIssuer() (string, error)
|
||||
GetSubject() (string, error)
|
||||
GetAudience() (ClaimStrings, error)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ var (
|
|||
ErrTokenExpired = errors.New("token is expired")
|
||||
ErrTokenUsedBeforeIssued = errors.New("token used before issued")
|
||||
ErrTokenInvalidIssuer = errors.New("token has invalid issuer")
|
||||
ErrTokenInvalidSubject = errors.New("token has invalid subject")
|
||||
ErrTokenNotValidYet = errors.New("token is not valid yet")
|
||||
ErrTokenInvalidId = errors.New("token has invalid id")
|
||||
ErrTokenInvalidClaims = errors.New("token has invalid claims")
|
||||
|
@ -29,11 +30,12 @@ const (
|
|||
ValidationErrorUnverifiable // Token could not be verified because of signing problems
|
||||
ValidationErrorSignatureInvalid // Signature validation failed
|
||||
|
||||
// Standard Claim validation errors
|
||||
// Registered Claim validation errors
|
||||
ValidationErrorAudience // AUD validation failed
|
||||
ValidationErrorExpired // EXP validation failed
|
||||
ValidationErrorIssuedAt // IAT validation failed
|
||||
ValidationErrorIssuer // ISS validation failed
|
||||
ValidationErrorSubject // SUB validation failed
|
||||
ValidationErrorNotValidYet // NBF validation failed
|
||||
ValidationErrorId // JTI validation failed
|
||||
ValidationErrorClaimsInvalid // Generic claims validation error
|
||||
|
|
|
@ -36,6 +36,11 @@ func (m MapClaims) GetIssuer() (string, error) {
|
|||
return m.ParseString("iss")
|
||||
}
|
||||
|
||||
// GetSubject implements the Claims interface.
|
||||
func (m MapClaims) GetSubject() (string, error) {
|
||||
return m.ParseString("sub")
|
||||
}
|
||||
|
||||
// ParseNumericDate tries to parse a key in the map claims type as a number
|
||||
// date. This will succeed, if the underlying type is either a [float64] or a
|
||||
// [json.Number]. Otherwise, nil will be returned.
|
||||
|
|
|
@ -56,3 +56,8 @@ func (c RegisteredClaims) GetAudience() (ClaimStrings, error) {
|
|||
func (c RegisteredClaims) GetIssuer() (string, error) {
|
||||
return c.Issuer, nil
|
||||
}
|
||||
|
||||
// GetSubject implements the Claims interface.
|
||||
func (c RegisteredClaims) GetSubject() (string, error) {
|
||||
return c.Subject, nil
|
||||
}
|
||||
|
|
62
validator.go
62
validator.go
|
@ -24,9 +24,17 @@ type Validator struct {
|
|||
// unrealistic, i.e., in the future.
|
||||
verifyIat bool
|
||||
|
||||
// expectedAud contains the audiences this token expects. Supplying an empty
|
||||
// expectedAud contains the audience this token expects. Supplying an empty
|
||||
// string will disable aud checking.
|
||||
expectedAud string
|
||||
|
||||
// expectedIss contains the issuer this token expects. Supplying an empty
|
||||
// string will disable iss checking.
|
||||
expectedIss string
|
||||
|
||||
// expectedSub contains the subject this token expects. Supplying an empty
|
||||
// string will disable sub checking.
|
||||
expectedSub string
|
||||
}
|
||||
|
||||
// CustomClaims represents a custom claims interface, which can be built upon the integrated
|
||||
|
@ -60,28 +68,42 @@ func (v *Validator) Validate(claims Claims) error {
|
|||
now = time.Now()
|
||||
}
|
||||
|
||||
// We always need to check the expiration time, but the claim itself is OPTIONAL
|
||||
if !v.VerifyExpiresAt(claims, now, false) {
|
||||
vErr.Inner = ErrTokenExpired
|
||||
vErr.Errors |= ValidationErrorExpired
|
||||
}
|
||||
|
||||
// Check iat if the option is enabled
|
||||
if v.verifyIat && !v.VerifyIssuedAt(claims, now, false) {
|
||||
vErr.Inner = ErrTokenUsedBeforeIssued
|
||||
vErr.Errors |= ValidationErrorIssuedAt
|
||||
}
|
||||
|
||||
// We always need to check not-before, but the claim itself is OPTIONAL
|
||||
if !v.VerifyNotBefore(claims, now, false) {
|
||||
vErr.Inner = ErrTokenNotValidYet
|
||||
vErr.Errors |= ValidationErrorNotValidYet
|
||||
}
|
||||
|
||||
// Check issued-at if the option is enabled
|
||||
if v.verifyIat && !v.VerifyIssuedAt(claims, now, false) {
|
||||
vErr.Inner = ErrTokenUsedBeforeIssued
|
||||
vErr.Errors |= ValidationErrorIssuedAt
|
||||
}
|
||||
|
||||
// If we have an expected audience, we also require the audience claim
|
||||
if v.expectedAud != "" && !v.VerifyAudience(claims, v.expectedAud, true) {
|
||||
vErr.Inner = ErrTokenInvalidAudience
|
||||
vErr.Errors |= ValidationErrorAudience
|
||||
}
|
||||
|
||||
// If we have an expected issuer, we also require the issuer claim
|
||||
if v.expectedIss != "" && !v.VerifyIssuer(claims, v.expectedIss, true) {
|
||||
vErr.Inner = ErrTokenInvalidIssuer
|
||||
vErr.Errors |= ValidationErrorIssuer
|
||||
}
|
||||
|
||||
// If we have an expected subject, we also require the subject claim
|
||||
if v.expectedSub != "" && !v.VerifySubject(claims, v.expectedSub, true) {
|
||||
vErr.Inner = ErrTokenInvalidSubject
|
||||
vErr.Errors |= ValidationErrorSubject
|
||||
}
|
||||
|
||||
// Finally, we want to give the claim itself some possibility to do some
|
||||
// additional custom validation based on their custom claims
|
||||
cvt, ok := claims.(CustomClaims)
|
||||
|
@ -166,6 +188,17 @@ func (v *Validator) VerifyIssuer(claims Claims, cmp string, req bool) bool {
|
|||
return verifyIss(iss, cmp, req)
|
||||
}
|
||||
|
||||
// VerifySubject compares the sub claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (v *Validator) VerifySubject(claims Claims, cmp string, req bool) bool {
|
||||
iss, err := claims.GetSubject()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return verifySub(iss, cmp, req)
|
||||
}
|
||||
|
||||
// ----- helpers
|
||||
|
||||
func verifyAud(aud []string, cmp string, required bool) bool {
|
||||
|
@ -221,9 +254,14 @@ func verifyIss(iss string, cmp string, required bool) bool {
|
|||
if iss == "" {
|
||||
return !required
|
||||
}
|
||||
if subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
return iss == cmp
|
||||
}
|
||||
|
||||
func verifySub(sub string, cmp string, required bool) bool {
|
||||
if sub == "" {
|
||||
return !required
|
||||
}
|
||||
|
||||
return sub == cmp
|
||||
}
|
||||
|
|
|
@ -33,9 +33,41 @@ func WithIssuedAt() ValidatorOption {
|
|||
}
|
||||
}
|
||||
|
||||
// WithAudience returns the ValidatorOption to set the expected audience.
|
||||
// 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.
|
||||
//
|
||||
// NOTE: While the `aud` claim is OPTIONAL is a JWT, the handling of it is
|
||||
// application-specific. Since this validation API is helping developers in
|
||||
// writing secure application, we decided to REQUIRE the existence of the claim.
|
||||
func WithAudience(aud string) ValidatorOption {
|
||||
return func(v *Validator) {
|
||||
v.expectedAud = aud
|
||||
}
|
||||
}
|
||||
|
||||
// WithIssuer configures the validator to require the specified issuer in the
|
||||
// `iss` claim. Validation will fail if a different issuer is specified in the
|
||||
// token or the `iss` claim is missing.
|
||||
//
|
||||
// NOTE: While the `iss` claim is OPTIONAL is a JWT, the handling of it is
|
||||
// application-specific. Since this validation API is helping developers in
|
||||
// writing secure application, we decided to REQUIRE the existence of the claim.
|
||||
func WithIssuer(iss string) ValidatorOption {
|
||||
return func(v *Validator) {
|
||||
v.expectedIss = iss
|
||||
}
|
||||
}
|
||||
|
||||
// WithSubject configures the validator to require the specified subject in the
|
||||
// `sub` claim. Validation will fail if a different subject is specified in the
|
||||
// token or the `sub` claim is missing.
|
||||
//
|
||||
// NOTE: While the `sub` claim is OPTIONAL is a JWT, the handling of it is
|
||||
// application-specific. Since this validation API is helping developers in
|
||||
// writing secure application, we decided to REQUIRE the existence of the claim.
|
||||
func WithSubject(sub string) ValidatorOption {
|
||||
return func(v *Validator) {
|
||||
v.expectedSub = sub
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue