This repository has been archived by the owner on May 24, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathconfig.go
232 lines (209 loc) · 7.81 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
package jwtware
import (
"errors"
"fmt"
"log"
"strings"
"time"
"github.com/MicahParks/keyfunc/v2"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5"
)
var (
// ErrJWTAlg is returned when the JWT header did not contain the expected algorithm.
ErrJWTAlg = errors.New("the JWT header did not contain the expected algorithm")
)
// Config defines the config for JWT middleware
type Config struct {
// Filter defines a function to skip middleware.
// Optional. Default: nil
Filter func(*fiber.Ctx) bool
// SuccessHandler defines a function which is executed for a valid token.
// Optional. Default: nil
SuccessHandler fiber.Handler
// ErrorHandler defines a function which is executed for an invalid token.
// It may be used to define a custom JWT error.
// Optional. Default: 401 Invalid or expired JWT
ErrorHandler fiber.ErrorHandler
// Signing key to validate token. Used as fallback if SigningKeys has length 0.
// At least one of the following is required: KeyFunc, JWKSetURLs, SigningKeys, or SigningKey.
// The order of precedence is: KeyFunc, JWKSetURLs, SigningKeys, SigningKey.
SigningKey SigningKey
// Map of signing keys to validate token with kid field usage.
// At least one of the following is required: KeyFunc, JWKSetURLs, SigningKeys, or SigningKey.
// The order of precedence is: KeyFunc, JWKSetURLs, SigningKeys, SigningKey.
SigningKeys map[string]SigningKey
// Context key to store user information from the token into context.
// Optional. Default: "user".
ContextKey string
// Claims are extendable claims data defining token content.
// Optional. Default value jwt.MapClaims
Claims jwt.Claims
// TokenLookup is a string in the form of "<source>:<name>" that is used
// to extract token from the request.
// Optional. Default value "header:Authorization".
// Possible values:
// - "header:<name>"
// - "query:<name>"
// - "param:<name>"
// - "cookie:<name>"
TokenLookup string
// AuthScheme to be used in the Authorization header.
// Optional. Default: "Bearer".
AuthScheme string
// KeyFunc is a function that supplies the public key for JWT cryptographic verification.
// The function shall take care of verifying the signing algorithm and selecting the proper key.
// Internally, github.com/MicahParks/keyfunc/v2 package is used project defaults. If you need more customization,
// you can provide a jwt.Keyfunc using that package or make your own implementation.
//
// At least one of the following is required: KeyFunc, JWKSetURLs, SigningKeys, or SigningKey.
// The order of precedence is: KeyFunc, JWKSetURLs, SigningKeys, SigningKey.
KeyFunc jwt.Keyfunc
// JWKSetURLs is a slice of HTTP URLs that contain the JSON Web Key Set (JWKS) used to verify the signatures of
// JWTs. Use of HTTPS is recommended. The presence of the "kid" field in the JWT header and JWKs is mandatory for
// this feature.
//
// By default, all JWK Sets in this slice will:
// * Refresh every hour.
// * Refresh automatically if a new "kid" is seen in a JWT being verified.
// * Rate limit refreshes to once every 5 minutes.
// * Timeout refreshes after 10 seconds.
//
// At least one of the following is required: KeyFunc, JWKSetURLs, SigningKeys, or SigningKey.
// The order of precedence is: KeyFunc, JWKSetURLs, SigningKeys, SigningKey.
JWKSetURLs []string
}
// SigningKey holds information about the recognized cryptographic keys used to sign JWTs by this program.
type SigningKey struct {
// JWTAlg is the algorithm used to sign JWTs. If this value is a non-empty string, this will be checked against the
// "alg" value in the JWT header.
//
// https://www.rfc-editor.org/rfc/rfc7518#section-3.1
JWTAlg string
// Key is the cryptographic key used to sign JWTs. For supported types, please see
// https://github.com/golang-jwt/jwt.
Key interface{}
}
// makeCfg function will check correctness of supplied configuration
// and will complement it with default values instead of missing ones
func makeCfg(config []Config) (cfg Config) {
if len(config) > 0 {
cfg = config[0]
}
if cfg.SuccessHandler == nil {
cfg.SuccessHandler = func(c *fiber.Ctx) error {
return c.Next()
}
}
if cfg.ErrorHandler == nil {
cfg.ErrorHandler = func(c *fiber.Ctx, err error) error {
if err.Error() == "Missing or malformed JWT" {
return c.Status(fiber.StatusBadRequest).SendString("Missing or malformed JWT")
}
return c.Status(fiber.StatusUnauthorized).SendString("Invalid or expired JWT")
}
}
if cfg.SigningKey.Key == nil && len(cfg.SigningKeys) == 0 && len(cfg.JWKSetURLs) == 0 && cfg.KeyFunc == nil {
panic("Fiber: JWT middleware configuration: At least one of the following is required: KeyFunc, JWKSetURLs, SigningKeys, or SigningKey.")
}
if cfg.ContextKey == "" {
cfg.ContextKey = "user"
}
if cfg.Claims == nil {
cfg.Claims = jwt.MapClaims{}
}
if cfg.TokenLookup == "" {
cfg.TokenLookup = defaultTokenLookup
// set AuthScheme as "Bearer" only if TokenLookup is set to default.
if cfg.AuthScheme == "" {
cfg.AuthScheme = "Bearer"
}
}
if cfg.KeyFunc == nil {
if len(cfg.SigningKeys) > 0 || len(cfg.JWKSetURLs) > 0 {
var givenKeys map[string]keyfunc.GivenKey
if cfg.SigningKeys != nil {
givenKeys = make(map[string]keyfunc.GivenKey, len(cfg.SigningKeys))
for kid, key := range cfg.SigningKeys {
givenKeys[kid] = keyfunc.NewGivenCustom(key, keyfunc.GivenKeyOptions{
Algorithm: key.JWTAlg,
})
}
}
if len(cfg.JWKSetURLs) > 0 {
var err error
cfg.KeyFunc, err = multiKeyfunc(givenKeys, cfg.JWKSetURLs)
if err != nil {
panic("Failed to create keyfunc from JWK Set URL: " + err.Error())
}
} else {
cfg.KeyFunc = keyfunc.NewGiven(givenKeys).Keyfunc
}
} else {
cfg.KeyFunc = signingKeyFunc(cfg.SigningKey)
}
}
return cfg
}
func multiKeyfunc(givenKeys map[string]keyfunc.GivenKey, jwkSetURLs []string) (jwt.Keyfunc, error) {
opts := keyfuncOptions(givenKeys)
multiple := make(map[string]keyfunc.Options, len(jwkSetURLs))
for _, url := range jwkSetURLs {
multiple[url] = opts
}
multiOpts := keyfunc.MultipleOptions{
KeySelector: keyfunc.KeySelectorFirst,
}
multi, err := keyfunc.GetMultiple(multiple, multiOpts)
if err != nil {
return nil, fmt.Errorf("failed to get multiple JWK Set URLs: %w", err)
}
return multi.Keyfunc, nil
}
func keyfuncOptions(givenKeys map[string]keyfunc.GivenKey) keyfunc.Options {
return keyfunc.Options{
GivenKeys: givenKeys,
RefreshErrorHandler: func(err error) {
log.Printf("Failed to perform background refresh of JWK Set: %s.", err)
},
RefreshInterval: time.Hour,
RefreshRateLimit: time.Minute * 5,
RefreshTimeout: time.Second * 10,
RefreshUnknownKID: true,
}
}
// getExtractors function will create a slice of functions which will be used
// for token sarch and will perform extraction of the value
func (cfg *Config) getExtractors() []jwtExtractor {
// Initialize
extractors := make([]jwtExtractor, 0)
rootParts := strings.Split(cfg.TokenLookup, ",")
for _, rootPart := range rootParts {
parts := strings.Split(strings.TrimSpace(rootPart), ":")
switch parts[0] {
case "header":
extractors = append(extractors, jwtFromHeader(parts[1], cfg.AuthScheme))
case "query":
extractors = append(extractors, jwtFromQuery(parts[1]))
case "param":
extractors = append(extractors, jwtFromParam(parts[1]))
case "cookie":
extractors = append(extractors, jwtFromCookie(parts[1]))
}
}
return extractors
}
func signingKeyFunc(key SigningKey) jwt.Keyfunc {
return func(token *jwt.Token) (interface{}, error) {
if key.JWTAlg != "" {
alg, ok := token.Header["alg"].(string)
if !ok {
return nil, fmt.Errorf("unexpected jwt signing method: expected: %q: got: missing or unexpected JSON type", key.JWTAlg)
}
if alg != key.JWTAlg {
return nil, fmt.Errorf("unexpected jwt signing method: expected: %q: got: %q", key.JWTAlg, alg)
}
}
return key.Key, nil
}
}