Skip to content

Commit 76f186a

Browse files
feat(jwt): make KeyFunc public in JWT middleware (#1756)
* feat(jwt): make KeyFunc public in JWT middleware It allows a user-defined function to supply the key for a token verification.
1 parent 6430665 commit 76f186a

File tree

2 files changed

+72
-23
lines changed

2 files changed

+72
-23
lines changed

middleware/jwt.go

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,19 @@ type (
2929
// ErrorHandlerWithContext is almost identical to ErrorHandler, but it's passed the current context.
3030
ErrorHandlerWithContext JWTErrorHandlerWithContext
3131

32-
// Signing key to validate token. Used as fallback if SigningKeys has length 0.
33-
// Required. This or SigningKeys.
32+
// Signing key to validate token.
33+
// This is one of the three options to provide a token validation key.
34+
// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
35+
// Required if neither user-defined KeyFunc nor SigningKeys is provided.
3436
SigningKey interface{}
3537

3638
// Map of signing keys to validate token with kid field usage.
37-
// Required. This or SigningKey.
39+
// This is one of the three options to provide a token validation key.
40+
// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
41+
// Required if neither user-defined KeyFunc nor SigningKey is provided.
3842
SigningKeys map[string]interface{}
3943

40-
// Signing method, used to check token signing method.
44+
// Signing method used to check the token's signing algorithm.
4145
// Optional. Default value HS256.
4246
SigningMethod string
4347

@@ -64,7 +68,16 @@ type (
6468
// Optional. Default value "Bearer".
6569
AuthScheme string
6670

67-
keyFunc jwt.Keyfunc
71+
// KeyFunc defines a user-defined function that supplies the public key for a token validation.
72+
// The function shall take care of verifying the signing algorithm and selecting the proper key.
73+
// A user-defined KeyFunc can be useful if tokens are issued by an external party.
74+
//
75+
// When a user-defined KeyFunc is provided, SigningKey, SigningKeys, and SigningMethod are ignored.
76+
// This is one of the three options to provide a token validation key.
77+
// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
78+
// Required if neither SigningKeys nor SigningKey is provided.
79+
// Default to an internal implementation verifying the signing algorithm and selecting the proper key.
80+
KeyFunc jwt.Keyfunc
6881
}
6982

7083
// JWTSuccessHandler defines a function which is executed for a valid token.
@@ -99,6 +112,7 @@ var (
99112
TokenLookup: "header:" + echo.HeaderAuthorization,
100113
AuthScheme: "Bearer",
101114
Claims: jwt.MapClaims{},
115+
KeyFunc: nil,
102116
}
103117
)
104118

@@ -123,7 +137,7 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
123137
if config.Skipper == nil {
124138
config.Skipper = DefaultJWTConfig.Skipper
125139
}
126-
if config.SigningKey == nil && len(config.SigningKeys) == 0 {
140+
if config.SigningKey == nil && len(config.SigningKeys) == 0 && config.KeyFunc == nil {
127141
panic("echo: jwt middleware requires signing key")
128142
}
129143
if config.SigningMethod == "" {
@@ -141,21 +155,8 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
141155
if config.AuthScheme == "" {
142156
config.AuthScheme = DefaultJWTConfig.AuthScheme
143157
}
144-
config.keyFunc = func(t *jwt.Token) (interface{}, error) {
145-
// Check the signing method
146-
if t.Method.Alg() != config.SigningMethod {
147-
return nil, fmt.Errorf("unexpected jwt signing method=%v", t.Header["alg"])
148-
}
149-
if len(config.SigningKeys) > 0 {
150-
if kid, ok := t.Header["kid"].(string); ok {
151-
if key, ok := config.SigningKeys[kid]; ok {
152-
return key, nil
153-
}
154-
}
155-
return nil, fmt.Errorf("unexpected jwt key id=%v", t.Header["kid"])
156-
}
157-
158-
return config.SigningKey, nil
158+
if config.KeyFunc == nil {
159+
config.KeyFunc = config.defaultKeyFunc
159160
}
160161

161162
// Initialize
@@ -196,11 +197,11 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
196197
token := new(jwt.Token)
197198
// Issue #647, #656
198199
if _, ok := config.Claims.(jwt.MapClaims); ok {
199-
token, err = jwt.Parse(auth, config.keyFunc)
200+
token, err = jwt.Parse(auth, config.KeyFunc)
200201
} else {
201202
t := reflect.ValueOf(config.Claims).Type().Elem()
202203
claims := reflect.New(t).Interface().(jwt.Claims)
203-
token, err = jwt.ParseWithClaims(auth, claims, config.keyFunc)
204+
token, err = jwt.ParseWithClaims(auth, claims, config.KeyFunc)
204205
}
205206
if err == nil && token.Valid {
206207
// Store user information from token into context.
@@ -225,6 +226,24 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
225226
}
226227
}
227228

229+
// defaultKeyFunc returns a signing key of the given token.
230+
func (config *JWTConfig) defaultKeyFunc(t *jwt.Token) (interface{}, error) {
231+
// Check the signing method
232+
if t.Method.Alg() != config.SigningMethod {
233+
return nil, fmt.Errorf("unexpected jwt signing method=%v", t.Header["alg"])
234+
}
235+
if len(config.SigningKeys) > 0 {
236+
if kid, ok := t.Header["kid"].(string); ok {
237+
if key, ok := config.SigningKeys[kid]; ok {
238+
return key, nil
239+
}
240+
}
241+
return nil, fmt.Errorf("unexpected jwt key id=%v", t.Header["kid"])
242+
}
243+
244+
return config.SigningKey, nil
245+
}
246+
228247
// jwtFromHeader returns a `jwtExtractor` that extracts token from the request header.
229248
func jwtFromHeader(header string, authScheme string) jwtExtractor {
230249
return func(c echo.Context) (string, error) {

middleware/jwt_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package middleware
22

33
import (
4+
"errors"
45
"net/http"
56
"net/http/httptest"
67
"net/url"
@@ -220,6 +221,35 @@ func TestJWT(t *testing.T) {
220221
expErrCode: http.StatusBadRequest,
221222
info: "Empty form field",
222223
},
224+
{
225+
hdrAuth: validAuth,
226+
config: JWTConfig{
227+
KeyFunc: func(*jwt.Token) (interface{}, error) {
228+
return validKey, nil
229+
},
230+
},
231+
info: "Valid JWT with a valid key using a user-defined KeyFunc",
232+
},
233+
{
234+
hdrAuth: validAuth,
235+
config: JWTConfig{
236+
KeyFunc: func(*jwt.Token) (interface{}, error) {
237+
return invalidKey, nil
238+
},
239+
},
240+
expErrCode: http.StatusUnauthorized,
241+
info: "Valid JWT with an invalid key using a user-defined KeyFunc",
242+
},
243+
{
244+
hdrAuth: validAuth,
245+
config: JWTConfig{
246+
KeyFunc: func(*jwt.Token) (interface{}, error) {
247+
return nil, errors.New("faulty KeyFunc")
248+
},
249+
},
250+
expErrCode: http.StatusUnauthorized,
251+
info: "Token verification does not pass using a user-defined KeyFunc",
252+
},
223253
} {
224254
if tc.reqURL == "" {
225255
tc.reqURL = "/"

0 commit comments

Comments
 (0)