forked from mirror/jwt
Structured Claims object!
Only verify claim if it isn't a default value. The alternative here would be to use pointers in the Claims structure then we would know which were nil, or if they were explicitly set to zero in the claim section Updated MapClaim implementation to check for existance of keys before using them. If they don't exists, validation functions simply return true.
This commit is contained in:
parent
febded4195
commit
44718f8a89
|
@ -18,6 +18,7 @@ const (
|
||||||
ValidationErrorSignatureInvalid // Signature validation failed
|
ValidationErrorSignatureInvalid // Signature validation failed
|
||||||
ValidationErrorExpired // Exp validation failed
|
ValidationErrorExpired // Exp validation failed
|
||||||
ValidationErrorNotValidYet // NBF validation failed
|
ValidationErrorNotValidYet // NBF validation failed
|
||||||
|
ValidationErrorIssuedAt // IAT validation failed
|
||||||
ValidationErrorClaimsInvalid // Generic claims validation error
|
ValidationErrorClaimsInvalid // Generic claims validation error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
183
jwt.go
183
jwt.go
|
@ -21,26 +21,45 @@ type Keyfunc func(*Token) (interface{}, error)
|
||||||
|
|
||||||
// For a type to be a Claims object, it must just have a Valid method that determines
|
// For a type to be a Claims object, it must just have a Valid method that determines
|
||||||
// if the token is invalid for any supported reason
|
// if the token is invalid for any supported reason
|
||||||
type Claims interface {
|
type Claimer interface {
|
||||||
Valid() error
|
Valid() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type MapClaim map[string]interface{}
|
// Structured version of Claims Section, as referenced at https://tools.ietf.org/html/rfc7519#section-4.1
|
||||||
|
type Claims struct {
|
||||||
|
Audience string `json:"aud,omitempty"`
|
||||||
|
ExpiresAt int64 `json:"exp,omitempty"`
|
||||||
|
Id string `json:"jti,omitempty"`
|
||||||
|
IssuedAt int64 `json:"iat,omitempty"`
|
||||||
|
Issuer string `json:"iss,omitempty"`
|
||||||
|
NotBefore int64 `json:"nbf,omitempty"`
|
||||||
|
Subject string `json:"sub,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
func (m MapClaim) Valid() error {
|
func (c Claims) Valid() error {
|
||||||
vErr := new(ValidationError)
|
vErr := new(ValidationError)
|
||||||
now := TimeFunc().Unix()
|
now := TimeFunc().Unix()
|
||||||
|
|
||||||
if exp, ok := m["exp"].(float64); ok {
|
// The claims below are optional, so if they are set to the default value in Go, let's not
|
||||||
if now > int64(exp) {
|
// verify them.
|
||||||
vErr.err = "token is expired"
|
|
||||||
|
if c.ExpiresAt != 0 {
|
||||||
|
if c.VerifyExpiresAt(now) == false {
|
||||||
|
vErr.err = "Token is expired"
|
||||||
vErr.Errors |= ValidationErrorExpired
|
vErr.Errors |= ValidationErrorExpired
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if nbf, ok := m["nbf"].(float64); ok {
|
if c.IssuedAt != 0 {
|
||||||
if now < int64(nbf) {
|
if c.VerifyIssuedAt(now) == false {
|
||||||
vErr.err = "token is not valid yet"
|
vErr.err = "Token used before issued, clock skew issue?"
|
||||||
|
vErr.Errors |= ValidationErrorIssuedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.NotBefore != 0 {
|
||||||
|
if c.VerifyNotBefore(now) == false {
|
||||||
|
vErr.err = "Token is not valid yet"
|
||||||
vErr.Errors |= ValidationErrorNotValidYet
|
vErr.Errors |= ValidationErrorNotValidYet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,13 +71,141 @@ func (m MapClaim) Valid() error {
|
||||||
return vErr
|
return vErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Claims) VerifyAudience(cmp string) bool {
|
||||||
|
return verifyAud(c.Audience, cmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Claims) VerifyExpiresAt(cmp int64) bool {
|
||||||
|
return verifyExp(c.ExpiresAt, cmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Claims) VerifyIssuedAt(cmp int64) bool {
|
||||||
|
return verifyIat(c.IssuedAt, cmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Claims) VerifyIssuer(cmp string) bool {
|
||||||
|
return verifyIss(c.Issuer, cmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Claims) VerifyNotBefore(cmp int64) bool {
|
||||||
|
return verifyNbf(c.NotBefore, cmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MapClaim map[string]interface{}
|
||||||
|
|
||||||
|
func (m MapClaim) VerifyAudience(cmp string) bool {
|
||||||
|
val, exists := m["aud"]
|
||||||
|
if !exists {
|
||||||
|
return true // Don't fail validation if claim doesn't exist
|
||||||
|
}
|
||||||
|
|
||||||
|
if aud, ok := val.(string); ok {
|
||||||
|
return verifyAud(aud, cmp)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MapClaim) VerifyExpiresAt(cmp int64) bool {
|
||||||
|
val, exists := m["exp"]
|
||||||
|
if !exists {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp, ok := val.(float64); ok {
|
||||||
|
return verifyExp(int64(exp), cmp)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MapClaim) VerifyIssuedAt(cmp int64) bool {
|
||||||
|
val, exists := m["iat"]
|
||||||
|
if !exists {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if iat, ok := val.(float64); ok {
|
||||||
|
return verifyIat(int64(iat), cmp)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MapClaim) VerifyIssuer(cmp string) bool {
|
||||||
|
val, exists := m["iss"]
|
||||||
|
if !exists {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if iss, ok := val.(string); ok {
|
||||||
|
return verifyIss(iss, cmp)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MapClaim) VerifyNotBefore(cmp int64) bool {
|
||||||
|
val, exists := m["nbf"]
|
||||||
|
if !exists {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if nbf, ok := val.(float64); ok {
|
||||||
|
return verifyNbf(int64(nbf), cmp)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MapClaim) Valid() error {
|
||||||
|
vErr := new(ValidationError)
|
||||||
|
now := TimeFunc().Unix()
|
||||||
|
|
||||||
|
if m.VerifyExpiresAt(now) == false {
|
||||||
|
vErr.err = "Token is expired"
|
||||||
|
vErr.Errors |= ValidationErrorExpired
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.VerifyIssuedAt(now) == false {
|
||||||
|
vErr.err = "Token used before issued, clock skew issue?"
|
||||||
|
vErr.Errors |= ValidationErrorIssuedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.VerifyNotBefore(now) == false {
|
||||||
|
vErr.err = "Token is not valid yet"
|
||||||
|
vErr.Errors |= ValidationErrorNotValidYet
|
||||||
|
}
|
||||||
|
|
||||||
|
if vErr.valid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return vErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyAud(aud string, cmp string) bool {
|
||||||
|
return aud == cmp
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyExp(exp int64, now int64) bool {
|
||||||
|
return now <= exp
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyIat(iat int64, now int64) bool {
|
||||||
|
return now >= iat
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyIss(iss string, cmp string) bool {
|
||||||
|
return iss == cmp
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyNbf(nbf int64, now int64) bool {
|
||||||
|
return now >= nbf
|
||||||
|
}
|
||||||
|
|
||||||
// A JWT Token. Different fields will be used depending on whether you're
|
// A JWT Token. Different fields will be used depending on whether you're
|
||||||
// creating or parsing/verifying a token.
|
// creating or parsing/verifying a token.
|
||||||
type Token struct {
|
type Token struct {
|
||||||
Raw string // The raw token. Populated when you Parse a token
|
Raw string // The raw token. Populated when you Parse a token
|
||||||
Method SigningMethod // The signing method used or to be used
|
Method SigningMethod // The signing method used or to be used
|
||||||
Header map[string]interface{} // The first segment of the token
|
Header map[string]interface{} // The first segment of the token
|
||||||
Claims Claims // The second segment of the token
|
Claims Claimer // The second segment of the token
|
||||||
Signature string // The third segment of the token. Populated when you Parse a token
|
Signature string // The third segment of the token. Populated when you Parse a token
|
||||||
Valid bool // Is the token valid? Populated when you Parse/Verify a token
|
Valid bool // Is the token valid? Populated when you Parse/Verify a token
|
||||||
}
|
}
|
||||||
|
@ -70,12 +217,12 @@ func New(method SigningMethod) *Token {
|
||||||
"typ": "JWT",
|
"typ": "JWT",
|
||||||
"alg": method.Alg(),
|
"alg": method.Alg(),
|
||||||
},
|
},
|
||||||
Claims: make(MapClaim),
|
Claims: Claims{},
|
||||||
Method: method,
|
Method: method,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWithClaims(method SigningMethod, claims Claims) *Token {
|
func NewWithClaims(method SigningMethod, claims Claimer) *Token {
|
||||||
return &Token{
|
return &Token{
|
||||||
Header: map[string]interface{}{
|
Header: map[string]interface{}{
|
||||||
"typ": "JWT",
|
"typ": "JWT",
|
||||||
|
@ -127,10 +274,10 @@ func (t *Token) SigningString() (string, error) {
|
||||||
// keyFunc will receive the parsed token and should return the key for validating.
|
// keyFunc will receive the parsed token and should return the key for validating.
|
||||||
// If everything is kosher, err will be nil
|
// If everything is kosher, err will be nil
|
||||||
func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
|
func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
|
||||||
return ParseWithClaims(tokenString, keyFunc, make(MapClaim))
|
return ParseWithClaims(tokenString, keyFunc, &Claims{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseWithClaims(tokenString string, keyFunc Keyfunc, claims Claims) (*Token, error) {
|
func ParseWithClaims(tokenString string, keyFunc Keyfunc, claims Claimer) (*Token, error) {
|
||||||
parts := strings.Split(tokenString, ".")
|
parts := strings.Split(tokenString, ".")
|
||||||
if len(parts) != 3 {
|
if len(parts) != 3 {
|
||||||
return nil, &ValidationError{err: "token contains an invalid number of segments", Errors: ValidationErrorMalformed}
|
return nil, &ValidationError{err: "token contains an invalid number of segments", Errors: ValidationErrorMalformed}
|
||||||
|
@ -182,13 +329,13 @@ func ParseWithClaims(tokenString string, keyFunc Keyfunc, claims Claims) (*Token
|
||||||
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorUnverifiable}
|
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorUnverifiable}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check expiration times
|
|
||||||
err = token.Claims.Valid()
|
|
||||||
var vErr *ValidationError
|
var vErr *ValidationError
|
||||||
|
|
||||||
|
// Validate Claims
|
||||||
|
if err := token.Claims.Valid(); err != nil {
|
||||||
|
|
||||||
// If the Claims Valid returned an error, check if it is a validation error,
|
// If the Claims Valid returned an error, check if it is a validation error,
|
||||||
// if not, convert it into one with a generic ClaimsInvalid flag set
|
// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
|
||||||
if err != nil {
|
|
||||||
if e, ok := err.(*ValidationError); !ok {
|
if e, ok := err.(*ValidationError); !ok {
|
||||||
vErr = &ValidationError{err: err.Error(), Errors: ValidationErrorClaimsInvalid}
|
vErr = &ValidationError{err: err.Error(), Errors: ValidationErrorClaimsInvalid}
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue