diff --git a/README.md b/README.md index 1435ddb..7907c0b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,63 @@ A [go](http://www.golang.org) (or 'golang' for search engine friendliness) imple **NOTICE:** A vulnerability in JWT was [recently published](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). As this library doesn't force users to validate the `alg` is what they expected, it's possible your usage is effected. There will be an update soon to remedy this, and it will likey require backwards-incompatible changes to the API. In the short term, please make sure your implementation verifies the `alg` is what you expect. +## Migration Guide from v2 -> v3 + +Added the ability to supply a typed object for the claims section of the token. + +Unfortunately this requires a breaking change. A few new methods were added to support this, +and the old default of `map[string]interface{}` was changed to `jwt.MapClaim`. + +The old example for creating a token looked like this.. + +```go + token := jwt.New(jwt.SigningMethodHS256) + token.Claims["foo"] = "bar" + token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix() +``` + +is now directly mapped to... + +```go + token := jwt.New(jwt.SigningMethodHS256) + claims := token.Claims.(jwt.MapClaim) + claims["foo"] = "bar" + claims["exp"] = time.Now().Add(time.Hour * 72).Unix() +``` + +However, we added a helper `jwt.NewWithClaims` which accepts a claims object. + +Any type can now be used as the claim object for inside a token so long as it implements the interface `jwt.Claims`. + +So, we added an additional claim type `jwt.StandardClaims` was added. +This is intended to be used as a base for creating your own types from, +and includes a few helper functions for verifying the claims defined [here](https://tools.ietf.org/html/rfc7519#section-4.1). + +```go + claims := jwt.StandardClaims{ + Audience: "myapi" + ExpiresAt: time.Now().Add(time.Hour * 72).Unix(), + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) +``` + +On the other end of usage all of the `jwt.Parse` and friends got a `WithClaims` suffix added to them. + +```go + token, err := jwt.Parse(token, keyFunc) + claims := token.Claims.(jwt.MapClaim) + //like you used to.. + claims["foo"] + claims["bar"] +``` + +New method usage: +```go + token, err := jwt.ParseWithClaims(token, keyFunc, &jwt.StandardClaims{}) + claims := token.Claims.(jwt.StandardClaims) + fmt.Println(claims.IssuedAt) +``` + ## What the heck is a JWT? In short, it's a signed JSON object that does something useful (for example, authentication). It's commonly used for `Bearer` tokens in Oauth 2. A token is made of three parts, separated by `.`'s. The first two parts are JSON objects, that have been [base64url](http://tools.ietf.org/html/rfc4648) encoded. The last part is the signature, encoded the same way. @@ -35,18 +92,18 @@ Parsing and verifying tokens is pretty straight forward. You pass in the token deliverUtterRejection(":(") } ``` - + ## Create a token ```go // Create the token - token := jwt.New(jwt.SigningMethodHS256) - // Set some claims - token.Claims["foo"] = "bar" - token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix() + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaim{ + "foo": "bar", + "exp": time.Now().Add(time.Hour * 72).Unix(), + }) // Sign and get the complete encoded token as a string tokenString, err := token.SignedString(mySigningKey) -``` +``` ## Project Status & Versioning diff --git a/claims.go b/claims.go new file mode 100644 index 0000000..a9bc533 --- /dev/null +++ b/claims.go @@ -0,0 +1,193 @@ +package jwt + +import "crypto/subtle" + +// 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 +type Claims interface { + Valid() error +} + +// Structured version of Claims Section, as referenced at +// https://tools.ietf.org/html/rfc7519#section-4.1 +type StandardClaims 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"` +} + +// Validates time based claims "exp, iat, nbf". +// There is no accounting for clock skew. +// As well, if any of the above claims are not in the token, it will still +// be considered a valid claim. +func (c StandardClaims) Valid() error { + vErr := new(ValidationError) + now := TimeFunc().Unix() + + // The claims below are optional, by default, so if they are set to the + // default value in Go, let's not fail the verification for them. + if c.VerifyExpiresAt(now, false) == false { + vErr.err = "Token is expired" + vErr.Errors |= ValidationErrorExpired + } + + if c.VerifyIssuedAt(now, false) == false { + vErr.err = "Token used before issued, clock skew issue?" + vErr.Errors |= ValidationErrorIssuedAt + } + + if c.VerifyNotBefore(now, false) == false { + vErr.err = "Token is not valid yet" + vErr.Errors |= ValidationErrorNotValidYet + } + + if vErr.valid() { + return nil + } + + return vErr +} + +// Compares the aud claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool { + return verifyAud(c.Audience, cmp, req) +} + +// Compares the exp claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool { + return verifyExp(c.ExpiresAt, cmp, req) +} + +// Compares the iat claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool { + return verifyIat(c.IssuedAt, cmp, req) +} + +// Compares the iss claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (c *StandardClaims) VerifyIssuer(cmp string, req bool) bool { + return verifyIss(c.Issuer, cmp, req) +} + +// Compares the nbf claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool { + return verifyNbf(c.NotBefore, cmp, req) +} + +type MapClaim map[string]interface{} + +// Compares the aud claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (m MapClaim) VerifyAudience(cmp string, req bool) bool { + aud, _ := m["aud"].(string) + return verifyAud(aud, cmp, req) +} + +// Compares the exp claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (m MapClaim) VerifyExpiresAt(cmp int64, req bool) bool { + exp, _ := m["exp"].(float64) + return verifyExp(int64(exp), cmp, req) +} + +// Compares the iat claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (m MapClaim) VerifyIssuedAt(cmp int64, req bool) bool { + iat, _ := m["iat"].(float64) + return verifyIat(int64(iat), cmp, req) +} + +// Compares the iss claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (m MapClaim) VerifyIssuer(cmp string, req bool) bool { + iss, _ := m["iss"].(string) + return verifyIss(iss, cmp, req) +} + +// Compares the nbf claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (m MapClaim) VerifyNotBefore(cmp int64, req bool) bool { + nbf, _ := m["nbf"].(float64) + return verifyNbf(int64(nbf), cmp, req) +} + +// Validates time based claims "exp, iat, nbf". +// There is no accounting for clock skew. +// As well, if any of the above claims are not in the token, it will still +// be considered a valid claim. +func (m MapClaim) Valid() error { + vErr := new(ValidationError) + now := TimeFunc().Unix() + + if m.VerifyExpiresAt(now, false) == false { + vErr.err = "Token is expired" + vErr.Errors |= ValidationErrorExpired + } + + if m.VerifyIssuedAt(now, false) == false { + vErr.err = "Token used before issued, clock skew issue?" + vErr.Errors |= ValidationErrorIssuedAt + } + + if m.VerifyNotBefore(now, false) == false { + vErr.err = "Token is not valid yet" + vErr.Errors |= ValidationErrorNotValidYet + } + + if vErr.valid() { + return nil + } + + return vErr +} + +func verifyAud(aud string, cmp string, required bool) bool { + if aud == "" { + return !required + } + if subtle.ConstantTimeCompare([]byte(aud), []byte(cmp)) != 0 { + return true + } else { + return false + } +} + +func verifyExp(exp int64, now int64, required bool) bool { + if exp == 0 { + return !required + } + return now <= exp +} + +func verifyIat(iat int64, now int64, required bool) bool { + if iat == 0 { + return !required + } + return now >= iat +} + +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 + } +} + +func verifyNbf(nbf int64, now int64, required bool) bool { + if nbf == 0 { + return !required + } + return now >= nbf +} diff --git a/cmd/jwt/app.go b/cmd/jwt/app.go index 62cb9a4..f4d51e8 100644 --- a/cmd/jwt/app.go +++ b/cmd/jwt/app.go @@ -155,7 +155,7 @@ func signToken() error { } // parse the JSON of the claims - var claims map[string]interface{} + var claims jwt.MapClaim if err := json.Unmarshal(tokData, &claims); err != nil { return fmt.Errorf("Couldn't parse claims JSON: %v", err) } @@ -173,8 +173,7 @@ func signToken() error { } // create a new token - token := jwt.New(alg) - token.Claims = claims + token := jwt.NewWithClaims(alg, claims) if out, err := token.SignedString(keyData); err == nil { fmt.Println(out) diff --git a/errors.go b/errors.go index e9e788f..b055f3b 100644 --- a/errors.go +++ b/errors.go @@ -16,8 +16,15 @@ const ( ValidationErrorMalformed uint32 = 1 << iota // Token is malformed ValidationErrorUnverifiable // Token could not be verified because of signing problems ValidationErrorSignatureInvalid // Signature validation failed - ValidationErrorExpired // Exp validation failed - ValidationErrorNotValidYet // NBF validation failed + + // Standard Claim validation errors + ValidationErrorAudience // AUD validation failed + ValidationErrorExpired // EXP validation failed + ValidationErrorIssuedAt // IAT validation failed + ValidationErrorIssuer // ISS validation failed + ValidationErrorNotValidYet // NBF validation failed + ValidationErrorId // JTI validation failed + ValidationErrorClaimsInvalid // Generic claims validation error ) // The error from Parse if token is not valid diff --git a/example_test.go b/example_test.go index edb48e4..224cd91 100644 --- a/example_test.go +++ b/example_test.go @@ -2,8 +2,9 @@ package jwt_test import ( "fmt" - "github.com/dgrijalva/jwt-go" "time" + + "github.com/dgrijalva/jwt-go" ) func ExampleParse(myToken string, myLookupKey func(interface{}) (interface{}, error)) { @@ -18,15 +19,28 @@ func ExampleParse(myToken string, myLookupKey func(interface{}) (interface{}, er } } -func ExampleNew(mySigningKey []byte) (string, error) { +func ExampleNew() { // Create the token - token := jwt.New(jwt.SigningMethodHS256) + token := jwt.New(jwt.SigningMethodRS256) + // Set some claims - token.Claims["foo"] = "bar" - token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix() - // Sign and get the complete encoded token as a string - tokenString, err := token.SignedString(mySigningKey) - return tokenString, err + claims := token.Claims.(jwt.MapClaim) + claims["foo"] = "bar" + claims["exp"] = time.Unix(0, 0).Add(time.Hour * 1).Unix() + + fmt.Printf("%v\n", token.Claims) + //Output: map[foo:bar exp:3600] +} + +func ExampleNewWithClaims(mySigningKey []byte) (string, error) { + // Create the Claims + claims := jwt.StandardClaims{ + ExpiresAt: 15000, + Issuer: "test", + } + + token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) + return token.SignedString(mySigningKey) } func ExampleParse_errorChecking(myToken string, myLookupKey func(interface{}) (interface{}, error)) { diff --git a/jwt.go b/jwt.go index 06995aa..b76c873 100644 --- a/jwt.go +++ b/jwt.go @@ -25,19 +25,23 @@ type Token struct { Raw string // The raw token. Populated when you Parse a token Method SigningMethod // The signing method used or to be used Header map[string]interface{} // The first segment of the token - Claims map[string]interface{} // The second segment of the token + Claims Claims // The second segment of the 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 } // Create a new Token. Takes a signing method func New(method SigningMethod) *Token { + return NewWithClaims(method, MapClaim{}) +} + +func NewWithClaims(method SigningMethod, claims Claims) *Token { return &Token{ Header: map[string]interface{}{ "typ": "JWT", "alg": method.Alg(), }, - Claims: make(map[string]interface{}), + Claims: claims, Method: method, } } @@ -63,16 +67,15 @@ func (t *Token) SigningString() (string, error) { var err error parts := make([]string, 2) for i, _ := range parts { - var source map[string]interface{} - if i == 0 { - source = t.Header - } else { - source = t.Claims - } - var jsonValue []byte - if jsonValue, err = json.Marshal(source); err != nil { - return "", err + if i == 0 { + if jsonValue, err = json.Marshal(t.Header); err != nil { + return "", err + } + } else { + if jsonValue, err = json.Marshal(t.Claims); err != nil { + return "", err + } } parts[i] = EncodeSegment(jsonValue) @@ -84,13 +87,20 @@ func (t *Token) SigningString() (string, error) { // keyFunc will receive the parsed token and should return the key for validating. // If everything is kosher, err will be nil func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { + return ParseWithClaims(tokenString, keyFunc, &MapClaim{}) +} + +func ParseWithClaims(tokenString string, keyFunc Keyfunc, claims Claims) (*Token, error) { parts := strings.Split(tokenString, ".") if len(parts) != 3 { return nil, &ValidationError{err: "token contains an invalid number of segments", Errors: ValidationErrorMalformed} } var err error - token := &Token{Raw: tokenString} + token := &Token{ + Raw: tokenString, + } + // parse Header var headerBytes []byte if headerBytes, err = DecodeSegment(parts[0]); err != nil { @@ -102,13 +112,17 @@ func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { // parse Claims var claimBytes []byte + if claimBytes, err = DecodeSegment(parts[1]); err != nil { return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} } - if err = json.Unmarshal(claimBytes, &token.Claims); err != nil { + + if err = json.Unmarshal(claimBytes, &claims); err != nil { return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} } + token.Claims = claims + // Lookup signature method if method, ok := token.Header["alg"].(string); ok { if token.Method = GetSigningMethod(method); token.Method == nil { @@ -129,19 +143,17 @@ func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { return token, &ValidationError{err: err.Error(), Errors: ValidationErrorUnverifiable} } - // Check expiration times vErr := &ValidationError{} - now := TimeFunc().Unix() - if exp, ok := token.Claims["exp"].(float64); ok { - if now > int64(exp) { - vErr.err = "token is expired" - vErr.Errors |= ValidationErrorExpired - } - } - if nbf, ok := token.Claims["nbf"].(float64); ok { - if now < int64(nbf) { - vErr.err = "token is not valid yet" - vErr.Errors |= ValidationErrorNotValidYet + + // Validate Claims + if err := token.Claims.Valid(); err != nil { + + // If the Claims Valid returned an error, check if it is a validation error, + // If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set + if e, ok := err.(*ValidationError); !ok { + vErr = &ValidationError{err: err.Error(), Errors: ValidationErrorClaimsInvalid} + } else { + vErr = e } } @@ -164,23 +176,25 @@ func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { // Currently, it looks in the Authorization header as well as // looking for an 'access_token' request parameter in req.Form. func ParseFromRequest(req *http.Request, keyFunc Keyfunc) (token *Token, err error) { + return ParseFromRequestWithClaims(req, keyFunc, &MapClaim{}) +} +func ParseFromRequestWithClaims(req *http.Request, keyFunc Keyfunc, claims Claims) (token *Token, err error) { // Look for an Authorization header if ah := req.Header.Get("Authorization"); ah != "" { // Should be a bearer token if len(ah) > 6 && strings.ToUpper(ah[0:6]) == "BEARER" { - return Parse(ah[7:], keyFunc) + return ParseWithClaims(ah[7:], keyFunc, claims) } } // Look for "access_token" parameter req.ParseMultipartForm(10e6) if tokStr := req.Form.Get("access_token"); tokStr != "" { - return Parse(tokStr, keyFunc) + return ParseWithClaims(tokStr, keyFunc, claims) } return nil, ErrNoTokenInRequest - } // Encode JWT specific base64url encoding with padding stripped diff --git a/jwt_test.go b/jwt_test.go index 15aeb30..43d0d25 100644 --- a/jwt_test.go +++ b/jwt_test.go @@ -3,12 +3,13 @@ package jwt_test import ( "crypto/rsa" "fmt" - "github.com/dgrijalva/jwt-go" "io/ioutil" "net/http" "reflect" "testing" "time" + + "github.com/dgrijalva/jwt-go" ) var ( @@ -23,7 +24,7 @@ var jwtTestData = []struct { name string tokenString string keyfunc jwt.Keyfunc - claims map[string]interface{} + claims jwt.MapClaim valid bool errors uint32 }{ @@ -31,7 +32,7 @@ var jwtTestData = []struct { "basic", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", defaultKeyFunc, - map[string]interface{}{"foo": "bar"}, + jwt.MapClaim{"foo": "bar"}, true, 0, }, @@ -39,7 +40,7 @@ var jwtTestData = []struct { "basic expired", "", // autogen defaultKeyFunc, - map[string]interface{}{"foo": "bar", "exp": float64(time.Now().Unix() - 100)}, + jwt.MapClaim{"foo": "bar", "exp": float64(time.Now().Unix() - 100)}, false, jwt.ValidationErrorExpired, }, @@ -47,7 +48,7 @@ var jwtTestData = []struct { "basic nbf", "", // autogen defaultKeyFunc, - map[string]interface{}{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)}, + jwt.MapClaim{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)}, false, jwt.ValidationErrorNotValidYet, }, @@ -55,7 +56,7 @@ var jwtTestData = []struct { "expired and nbf", "", // autogen defaultKeyFunc, - map[string]interface{}{"foo": "bar", "nbf": float64(time.Now().Unix() + 100), "exp": float64(time.Now().Unix() - 100)}, + jwt.MapClaim{"foo": "bar", "nbf": float64(time.Now().Unix() + 100), "exp": float64(time.Now().Unix() - 100)}, false, jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired, }, @@ -63,7 +64,7 @@ var jwtTestData = []struct { "basic invalid", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", defaultKeyFunc, - map[string]interface{}{"foo": "bar"}, + jwt.MapClaim{"foo": "bar"}, false, jwt.ValidationErrorSignatureInvalid, }, @@ -71,7 +72,7 @@ var jwtTestData = []struct { "basic nokeyfunc", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", nilKeyFunc, - map[string]interface{}{"foo": "bar"}, + jwt.MapClaim{"foo": "bar"}, false, jwt.ValidationErrorUnverifiable, }, @@ -79,7 +80,7 @@ var jwtTestData = []struct { "basic nokey", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", emptyKeyFunc, - map[string]interface{}{"foo": "bar"}, + jwt.MapClaim{"foo": "bar"}, false, jwt.ValidationErrorSignatureInvalid, }, @@ -87,7 +88,7 @@ var jwtTestData = []struct { "basic errorkey", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", errorKeyFunc, - map[string]interface{}{"foo": "bar"}, + jwt.MapClaim{"foo": "bar"}, false, jwt.ValidationErrorUnverifiable, }, @@ -103,7 +104,7 @@ func init() { } } -func makeSample(c map[string]interface{}) string { +func makeSample(c jwt.MapClaim) string { keyData, e := ioutil.ReadFile("test/sample_key") if e != nil { panic(e.Error()) @@ -113,8 +114,7 @@ func makeSample(c map[string]interface{}) string { panic(e.Error()) } - token := jwt.New(jwt.SigningMethodRS256) - token.Claims = c + token := jwt.NewWithClaims(jwt.SigningMethodRS256, c) s, e := token.SignedString(key) if e != nil { @@ -129,17 +129,21 @@ func TestJWT(t *testing.T) { if data.tokenString == "" { data.tokenString = makeSample(data.claims) } - token, err := jwt.Parse(data.tokenString, data.keyfunc) - if !reflect.DeepEqual(data.claims, token.Claims) { + token, err := jwt.ParseWithClaims(data.tokenString, data.keyfunc, &jwt.MapClaim{}) + + if !reflect.DeepEqual(&data.claims, token.Claims) { t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims) } + if data.valid && err != nil { t.Errorf("[%v] Error while verifying token: %T:%v", data.name, err, err) } + if !data.valid && err == nil { t.Errorf("[%v] Invalid token passed validation", data.name) } + if data.errors != 0 { if err == nil { t.Errorf("[%v] Expecting error. Didn't get one.", data.name) @@ -163,13 +167,13 @@ func TestParseRequest(t *testing.T) { r, _ := http.NewRequest("GET", "/", nil) r.Header.Set("Authorization", fmt.Sprintf("Bearer %v", data.tokenString)) - token, err := jwt.ParseFromRequest(r, data.keyfunc) + token, err := jwt.ParseFromRequestWithClaims(r, data.keyfunc, &jwt.MapClaim{}) if token == nil { t.Errorf("[%v] Token was not found: %v", data.name, err) continue } - if !reflect.DeepEqual(data.claims, token.Claims) { + if !reflect.DeepEqual(&data.claims, token.Claims) { t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims) } if data.valid && err != nil {