Merge branch 'custom-parsing' of https://github.com/itsjamie/jwt-go into release_3_0_0

This commit is contained in:
Dave Grijalva 2015-08-18 09:30:39 -07:00
commit 17be525a73
7 changed files with 352 additions and 64 deletions

View File

@ -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. **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? ## 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. 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.
@ -40,10 +97,10 @@ Parsing and verifying tokens is pretty straight forward. You pass in the token
```go ```go
// Create the token // Create the token
token := jwt.New(jwt.SigningMethodHS256) token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaim{
// Set some claims "foo": "bar",
token.Claims["foo"] = "bar" "exp": time.Now().Add(time.Hour * 72).Unix(),
token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix() })
// Sign and get the complete encoded token as a string // Sign and get the complete encoded token as a string
tokenString, err := token.SignedString(mySigningKey) tokenString, err := token.SignedString(mySigningKey)
``` ```

193
claims.go Normal file
View File

@ -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
}

View File

@ -155,7 +155,7 @@ func signToken() error {
} }
// parse the JSON of the claims // parse the JSON of the claims
var claims map[string]interface{} var claims jwt.MapClaim
if err := json.Unmarshal(tokData, &claims); err != nil { if err := json.Unmarshal(tokData, &claims); err != nil {
return fmt.Errorf("Couldn't parse claims JSON: %v", err) return fmt.Errorf("Couldn't parse claims JSON: %v", err)
} }
@ -173,8 +173,7 @@ func signToken() error {
} }
// create a new token // create a new token
token := jwt.New(alg) token := jwt.NewWithClaims(alg, claims)
token.Claims = claims
if out, err := token.SignedString(keyData); err == nil { if out, err := token.SignedString(keyData); err == nil {
fmt.Println(out) fmt.Println(out)

View File

@ -16,8 +16,15 @@ const (
ValidationErrorMalformed uint32 = 1 << iota // Token is malformed ValidationErrorMalformed uint32 = 1 << iota // Token is malformed
ValidationErrorUnverifiable // Token could not be verified because of signing problems ValidationErrorUnverifiable // Token could not be verified because of signing problems
ValidationErrorSignatureInvalid // Signature validation failed ValidationErrorSignatureInvalid // Signature validation failed
ValidationErrorExpired // Exp 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 ValidationErrorNotValidYet // NBF validation failed
ValidationErrorId // JTI validation failed
ValidationErrorClaimsInvalid // Generic claims validation error
) )
// The error from Parse if token is not valid // The error from Parse if token is not valid

View File

@ -2,8 +2,9 @@ package jwt_test
import ( import (
"fmt" "fmt"
"github.com/dgrijalva/jwt-go"
"time" "time"
"github.com/dgrijalva/jwt-go"
) )
func ExampleParse(myToken string, myLookupKey func(interface{}) (interface{}, error)) { 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 // Create the token
token := jwt.New(jwt.SigningMethodHS256) token := jwt.New(jwt.SigningMethodRS256)
// Set some claims // Set some claims
token.Claims["foo"] = "bar" claims := token.Claims.(jwt.MapClaim)
token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix() claims["foo"] = "bar"
// Sign and get the complete encoded token as a string claims["exp"] = time.Unix(0, 0).Add(time.Hour * 1).Unix()
tokenString, err := token.SignedString(mySigningKey)
return tokenString, err 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)) { func ExampleParse_errorChecking(myToken string, myLookupKey func(interface{}) (interface{}, error)) {

68
jwt.go
View File

@ -25,19 +25,23 @@ 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 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 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
} }
// Create a new Token. Takes a signing method // Create a new Token. Takes a signing method
func New(method SigningMethod) *Token { func New(method SigningMethod) *Token {
return NewWithClaims(method, MapClaim{})
}
func NewWithClaims(method SigningMethod, claims Claims) *Token {
return &Token{ return &Token{
Header: map[string]interface{}{ Header: map[string]interface{}{
"typ": "JWT", "typ": "JWT",
"alg": method.Alg(), "alg": method.Alg(),
}, },
Claims: make(map[string]interface{}), Claims: claims,
Method: method, Method: method,
} }
} }
@ -63,17 +67,16 @@ func (t *Token) SigningString() (string, error) {
var err error var err error
parts := make([]string, 2) parts := make([]string, 2)
for i, _ := range parts { for i, _ := range parts {
var source map[string]interface{}
if i == 0 {
source = t.Header
} else {
source = t.Claims
}
var jsonValue []byte var jsonValue []byte
if jsonValue, err = json.Marshal(source); err != nil { if i == 0 {
if jsonValue, err = json.Marshal(t.Header); err != nil {
return "", err return "", err
} }
} else {
if jsonValue, err = json.Marshal(t.Claims); err != nil {
return "", err
}
}
parts[i] = EncodeSegment(jsonValue) 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. // 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, &MapClaim{})
}
func ParseWithClaims(tokenString string, keyFunc Keyfunc, claims Claims) (*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}
} }
var err error var err error
token := &Token{Raw: tokenString} token := &Token{
Raw: tokenString,
}
// parse Header // parse Header
var headerBytes []byte var headerBytes []byte
if headerBytes, err = DecodeSegment(parts[0]); err != nil { if headerBytes, err = DecodeSegment(parts[0]); err != nil {
@ -102,13 +112,17 @@ func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
// parse Claims // parse Claims
var claimBytes []byte var claimBytes []byte
if claimBytes, err = DecodeSegment(parts[1]); err != nil { if claimBytes, err = DecodeSegment(parts[1]); err != nil {
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} 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} return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed}
} }
token.Claims = claims
// Lookup signature method // Lookup signature method
if method, ok := token.Header["alg"].(string); ok { if method, ok := token.Header["alg"].(string); ok {
if token.Method = GetSigningMethod(method); token.Method == nil { 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} return token, &ValidationError{err: err.Error(), Errors: ValidationErrorUnverifiable}
} }
// Check expiration times
vErr := &ValidationError{} vErr := &ValidationError{}
now := TimeFunc().Unix()
if exp, ok := token.Claims["exp"].(float64); ok { // Validate Claims
if now > int64(exp) { if err := token.Claims.Valid(); err != nil {
vErr.err = "token is expired"
vErr.Errors |= ValidationErrorExpired // 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 {
if nbf, ok := token.Claims["nbf"].(float64); ok { vErr = &ValidationError{err: err.Error(), Errors: ValidationErrorClaimsInvalid}
if now < int64(nbf) { } else {
vErr.err = "token is not valid yet" vErr = e
vErr.Errors |= ValidationErrorNotValidYet
} }
} }
@ -164,23 +176,25 @@ func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
// Currently, it looks in the Authorization header as well as // Currently, it looks in the Authorization header as well as
// looking for an 'access_token' request parameter in req.Form. // looking for an 'access_token' request parameter in req.Form.
func ParseFromRequest(req *http.Request, keyFunc Keyfunc) (token *Token, err error) { 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 // Look for an Authorization header
if ah := req.Header.Get("Authorization"); ah != "" { if ah := req.Header.Get("Authorization"); ah != "" {
// Should be a bearer token // Should be a bearer token
if len(ah) > 6 && strings.ToUpper(ah[0:6]) == "BEARER" { 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 // Look for "access_token" parameter
req.ParseMultipartForm(10e6) req.ParseMultipartForm(10e6)
if tokStr := req.Form.Get("access_token"); tokStr != "" { if tokStr := req.Form.Get("access_token"); tokStr != "" {
return Parse(tokStr, keyFunc) return ParseWithClaims(tokStr, keyFunc, claims)
} }
return nil, ErrNoTokenInRequest return nil, ErrNoTokenInRequest
} }
// Encode JWT specific base64url encoding with padding stripped // Encode JWT specific base64url encoding with padding stripped

View File

@ -3,12 +3,13 @@ package jwt_test
import ( import (
"crypto/rsa" "crypto/rsa"
"fmt" "fmt"
"github.com/dgrijalva/jwt-go"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"reflect" "reflect"
"testing" "testing"
"time" "time"
"github.com/dgrijalva/jwt-go"
) )
var ( var (
@ -23,7 +24,7 @@ var jwtTestData = []struct {
name string name string
tokenString string tokenString string
keyfunc jwt.Keyfunc keyfunc jwt.Keyfunc
claims map[string]interface{} claims jwt.MapClaim
valid bool valid bool
errors uint32 errors uint32
}{ }{
@ -31,7 +32,7 @@ var jwtTestData = []struct {
"basic", "basic",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
defaultKeyFunc, defaultKeyFunc,
map[string]interface{}{"foo": "bar"}, jwt.MapClaim{"foo": "bar"},
true, true,
0, 0,
}, },
@ -39,7 +40,7 @@ var jwtTestData = []struct {
"basic expired", "basic expired",
"", // autogen "", // autogen
defaultKeyFunc, defaultKeyFunc,
map[string]interface{}{"foo": "bar", "exp": float64(time.Now().Unix() - 100)}, jwt.MapClaim{"foo": "bar", "exp": float64(time.Now().Unix() - 100)},
false, false,
jwt.ValidationErrorExpired, jwt.ValidationErrorExpired,
}, },
@ -47,7 +48,7 @@ var jwtTestData = []struct {
"basic nbf", "basic nbf",
"", // autogen "", // autogen
defaultKeyFunc, defaultKeyFunc,
map[string]interface{}{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)}, jwt.MapClaim{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)},
false, false,
jwt.ValidationErrorNotValidYet, jwt.ValidationErrorNotValidYet,
}, },
@ -55,7 +56,7 @@ var jwtTestData = []struct {
"expired and nbf", "expired and nbf",
"", // autogen "", // autogen
defaultKeyFunc, 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, false,
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired, jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
}, },
@ -63,7 +64,7 @@ var jwtTestData = []struct {
"basic invalid", "basic invalid",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
defaultKeyFunc, defaultKeyFunc,
map[string]interface{}{"foo": "bar"}, jwt.MapClaim{"foo": "bar"},
false, false,
jwt.ValidationErrorSignatureInvalid, jwt.ValidationErrorSignatureInvalid,
}, },
@ -71,7 +72,7 @@ var jwtTestData = []struct {
"basic nokeyfunc", "basic nokeyfunc",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
nilKeyFunc, nilKeyFunc,
map[string]interface{}{"foo": "bar"}, jwt.MapClaim{"foo": "bar"},
false, false,
jwt.ValidationErrorUnverifiable, jwt.ValidationErrorUnverifiable,
}, },
@ -79,7 +80,7 @@ var jwtTestData = []struct {
"basic nokey", "basic nokey",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
emptyKeyFunc, emptyKeyFunc,
map[string]interface{}{"foo": "bar"}, jwt.MapClaim{"foo": "bar"},
false, false,
jwt.ValidationErrorSignatureInvalid, jwt.ValidationErrorSignatureInvalid,
}, },
@ -87,7 +88,7 @@ var jwtTestData = []struct {
"basic errorkey", "basic errorkey",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
errorKeyFunc, errorKeyFunc,
map[string]interface{}{"foo": "bar"}, jwt.MapClaim{"foo": "bar"},
false, false,
jwt.ValidationErrorUnverifiable, 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") keyData, e := ioutil.ReadFile("test/sample_key")
if e != nil { if e != nil {
panic(e.Error()) panic(e.Error())
@ -113,8 +114,7 @@ func makeSample(c map[string]interface{}) string {
panic(e.Error()) panic(e.Error())
} }
token := jwt.New(jwt.SigningMethodRS256) token := jwt.NewWithClaims(jwt.SigningMethodRS256, c)
token.Claims = c
s, e := token.SignedString(key) s, e := token.SignedString(key)
if e != nil { if e != nil {
@ -129,17 +129,21 @@ func TestJWT(t *testing.T) {
if data.tokenString == "" { if data.tokenString == "" {
data.tokenString = makeSample(data.claims) 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) t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims)
} }
if data.valid && err != nil { if data.valid && err != nil {
t.Errorf("[%v] Error while verifying token: %T:%v", data.name, err, err) t.Errorf("[%v] Error while verifying token: %T:%v", data.name, err, err)
} }
if !data.valid && err == nil { if !data.valid && err == nil {
t.Errorf("[%v] Invalid token passed validation", data.name) t.Errorf("[%v] Invalid token passed validation", data.name)
} }
if data.errors != 0 { if data.errors != 0 {
if err == nil { if err == nil {
t.Errorf("[%v] Expecting error. Didn't get one.", data.name) 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, _ := http.NewRequest("GET", "/", nil)
r.Header.Set("Authorization", fmt.Sprintf("Bearer %v", data.tokenString)) 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 { if token == nil {
t.Errorf("[%v] Token was not found: %v", data.name, err) t.Errorf("[%v] Token was not found: %v", data.name, err)
continue 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) t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims)
} }
if data.valid && err != nil { if data.valid && err != nil {