7 jwt "github.com/dgrijalva/jwt-go"
9 "github.com/go-kit/kit/endpoint"
12 type contextKey string
15 // JWTTokenContextKey holds the key used to store a JWT Token in the
17 JWTTokenContextKey contextKey = "JWTToken"
19 // JWTClaimsContextKey holds the key used to store the JWT Claims in the
21 JWTClaimsContextKey contextKey = "JWTClaims"
25 // ErrTokenContextMissing denotes a token was not passed into the parsing
26 // middleware's context.
27 ErrTokenContextMissing = errors.New("token up for parsing was not passed through the context")
29 // ErrTokenInvalid denotes a token was not able to be validated.
30 ErrTokenInvalid = errors.New("JWT Token was invalid")
32 // ErrTokenExpired denotes a token's expire header (exp) has since passed.
33 ErrTokenExpired = errors.New("JWT Token is expired")
35 // ErrTokenMalformed denotes a token was not formatted as a JWT token.
36 ErrTokenMalformed = errors.New("JWT Token is malformed")
38 // ErrTokenNotActive denotes a token's not before header (nbf) is in the
40 ErrTokenNotActive = errors.New("token is not valid yet")
42 // ErrUnexpectedSigningMethod denotes a token was signed with an unexpected
44 ErrUnexpectedSigningMethod = errors.New("unexpected signing method")
47 // NewSigner creates a new JWT token generating middleware, specifying key ID,
48 // signing string, signing method and the claims you would like it to contain.
49 // Tokens are signed with a Key ID header (kid) which is useful for determining
50 // the key to use for parsing. Particularly useful for clients.
51 func NewSigner(kid string, key []byte, method jwt.SigningMethod, claims jwt.Claims) endpoint.Middleware {
52 return func(next endpoint.Endpoint) endpoint.Endpoint {
53 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
54 token := jwt.NewWithClaims(method, claims)
55 token.Header["kid"] = kid
57 // Sign and get the complete encoded token as a string using the secret
58 tokenString, err := token.SignedString(key)
62 ctx = context.WithValue(ctx, JWTTokenContextKey, tokenString)
64 return next(ctx, request)
69 // ClaimsFactory is a factory for jwt.Claims.
70 // Useful in NewParser middleware.
71 type ClaimsFactory func() jwt.Claims
73 // MapClaimsFactory is a ClaimsFactory that returns
74 // an empty jwt.MapClaims.
75 func MapClaimsFactory() jwt.Claims {
76 return jwt.MapClaims{}
79 // StandardClaimsFactory is a ClaimsFactory that returns
80 // an empty jwt.StandardClaims.
81 func StandardClaimsFactory() jwt.Claims {
82 return &jwt.StandardClaims{}
85 // NewParser creates a new JWT token parsing middleware, specifying a
86 // jwt.Keyfunc interface, the signing method and the claims type to be used. NewParser
87 // adds the resulting claims to endpoint context or returns error on invalid token.
88 // Particularly useful for servers.
89 func NewParser(keyFunc jwt.Keyfunc, method jwt.SigningMethod, newClaims ClaimsFactory) endpoint.Middleware {
90 return func(next endpoint.Endpoint) endpoint.Endpoint {
91 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
92 // tokenString is stored in the context from the transport handlers.
93 tokenString, ok := ctx.Value(JWTTokenContextKey).(string)
95 return nil, ErrTokenContextMissing
98 // Parse takes the token string and a function for looking up the
99 // key. The latter is especially useful if you use multiple keys
100 // for your application. The standard is to use 'kid' in the head
101 // of the token to identify which key to use, but the parsed token
102 // (head and claims) is provided to the callback, providing
104 token, err := jwt.ParseWithClaims(tokenString, newClaims(), func(token *jwt.Token) (interface{}, error) {
105 // Don't forget to validate the alg is what you expect:
106 if token.Method != method {
107 return nil, ErrUnexpectedSigningMethod
110 return keyFunc(token)
113 if e, ok := err.(*jwt.ValidationError); ok {
115 case e.Errors&jwt.ValidationErrorMalformed != 0:
116 // Token is malformed
117 return nil, ErrTokenMalformed
118 case e.Errors&jwt.ValidationErrorExpired != 0:
120 return nil, ErrTokenExpired
121 case e.Errors&jwt.ValidationErrorNotValidYet != 0:
122 // Token is not active yet
123 return nil, ErrTokenNotActive
128 // We have a ValidationError but have no specific Go kit error for it.
129 // Fall through to return original error.
135 return nil, ErrTokenInvalid
138 ctx = context.WithValue(ctx, JWTClaimsContextKey, token.Claims)
140 return next(ctx, request)