-
Notifications
You must be signed in to change notification settings - Fork 91
/
jwt.go
262 lines (219 loc) · 6.86 KB
/
jwt.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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
package jwt
import (
"errors"
"fmt"
"strings"
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/context"
"github.com/golang-jwt/jwt/v4"
)
func init() {
context.SetHandlerName("github.com/iris-contrib/middleware/jwt.*", "iris-contrib.jwt")
}
type (
// Token for JWT. Different fields will be used depending on whether you're
// creating or parsing/verifying a token.
//
// A type alias for jwt.Token.
Token = jwt.Token
// MapClaims type that uses the map[string]interface{} for JSON decoding
// This is the default claims type if you don't supply one
//
// A type alias for jwt.MapClaims.
MapClaims = jwt.MapClaims
// Claims must just have a Valid method that determines
// if the token is invalid for any supported reason.
//
// A type alias for jwt.Claims.
Claims = jwt.Claims
)
// Shortcuts to create a new Token.
var (
NewToken = jwt.New
NewTokenWithClaims = jwt.NewWithClaims
)
// HS256 and company.
var (
SigningMethodHS256 = jwt.SigningMethodHS256
SigningMethodHS384 = jwt.SigningMethodHS384
SigningMethodHS512 = jwt.SigningMethodHS512
)
// ECDSA - EC256 and company.
var (
SigningMethodES256 = jwt.SigningMethodES256
SigningMethodES384 = jwt.SigningMethodES384
SigningMethodES512 = jwt.SigningMethodES512
)
// A function called whenever an error is encountered
type errorHandler func(iris.Context, error)
// TokenExtractor is a function that takes a context as input and returns
// either a token or an error. An error should only be returned if an attempt
// to specify a token was found, but the information was somehow incorrectly
// formed. In the case where a token is simply not present, this should not
// be treated as an error. An empty string should be returned in that case.
type TokenExtractor func(iris.Context) (string, error)
// Middleware the middleware for JSON Web tokens authentication method
type Middleware struct {
Config Config
}
// OnError is the default error handler.
// Use it to change the behavior for each error.
// See `Config.ErrorHandler`.
func OnError(ctx iris.Context, err error) {
if err == nil {
return
}
ctx.StopExecution()
ctx.StatusCode(iris.StatusUnauthorized)
ctx.WriteString(err.Error())
}
// New constructs a new Secure instance with supplied options.
func New(cfg ...Config) *Middleware {
var c Config
if len(cfg) == 0 {
c = Config{}
} else {
c = cfg[0]
}
if c.ContextKey == "" {
c.ContextKey = DefaultContextKey
}
if c.ErrorHandler == nil {
c.ErrorHandler = OnError
}
if c.Extractor == nil {
c.Extractor = FromAuthHeader
}
return &Middleware{Config: c}
}
func logf(ctx iris.Context, format string, args ...interface{}) {
ctx.Application().Logger().Debugf(format, args...)
}
// Get returns the user (&token) information for this client/request
func (m *Middleware) Get(ctx iris.Context) *jwt.Token {
v := ctx.Values().Get(m.Config.ContextKey)
if v == nil {
return nil
}
return v.(*jwt.Token)
}
// Serve the middleware's action
func (m *Middleware) Serve(ctx iris.Context) {
if err := m.CheckJWT(ctx); err != nil {
m.Config.ErrorHandler(ctx, err)
return
}
// If everything ok then call next.
ctx.Next()
}
// FromAuthHeader is a "TokenExtractor" that takes a give context and extracts
// the JWT token from the Authorization header.
func FromAuthHeader(ctx iris.Context) (string, error) {
authHeader := ctx.GetHeader("Authorization")
if authHeader == "" {
return "", nil // No error, just no token
}
// TODO: Make this a bit more robust, parsing-wise
authHeaderParts := strings.Split(authHeader, " ")
if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
return "", fmt.Errorf("authorization header format must be Bearer {token}")
}
return authHeaderParts[1], nil
}
// FromParameter returns a function that extracts the token from the specified
// query string parameter
func FromParameter(param string) TokenExtractor {
return func(ctx iris.Context) (string, error) {
return ctx.URLParam(param), nil
}
}
// FromFirst returns a function that runs multiple token extractors and takes the
// first token it finds
func FromFirst(extractors ...TokenExtractor) TokenExtractor {
return func(ctx iris.Context) (string, error) {
for _, ex := range extractors {
token, err := ex(ctx)
if err != nil {
return "", err
}
if token != "" {
return token, nil
}
}
return "", nil
}
}
var (
// ErrTokenMissing is the error value that it's returned when
// a token is not found based on the token extractor.
ErrTokenMissing = errors.New("required authorization token not found")
// ErrTokenInvalid is the error value that it's returned when
// a token is not valid.
ErrTokenInvalid = errors.New("token is invalid")
// ErrTokenExpired is the error value that it's returned when
// a token value is found and it's valid but it's expired.
ErrTokenExpired = errors.New("token is expired")
)
var jwtParser = new(jwt.Parser)
// CheckJWT the main functionality, checks for token
func (m *Middleware) CheckJWT(ctx iris.Context) error {
if !m.Config.EnableAuthOnOptions {
if ctx.Method() == iris.MethodOptions {
return nil
}
}
// Use the specified token extractor to extract a token from the request
token, err := m.Config.Extractor(ctx)
// If debugging is turned on, log the outcome
if err != nil {
logf(ctx, "Error extracting JWT: %v", err)
return err
}
logf(ctx, "Token extracted: %s", token)
// If the token is empty...
if token == "" {
// Check if it was required
if m.Config.CredentialsOptional {
logf(ctx, "No credentials found (CredentialsOptional=true)")
// No error, just no token (and that is ok given that CredentialsOptional is true)
return nil
}
// If we get here, the required token is missing
logf(ctx, "Error: No credentials found (CredentialsOptional=false)")
return ErrTokenMissing
}
// Now parse the token
parsedToken, err := jwtParser.Parse(token, m.Config.ValidationKeyGetter)
// Check if there was an error in parsing...
if err != nil {
logf(ctx, "Error parsing token: %v", err)
return err
}
if m.Config.SigningMethod != nil && m.Config.SigningMethod.Alg() != parsedToken.Header["alg"] {
err := fmt.Errorf("expected %s signing method but token specified %s",
m.Config.SigningMethod.Alg(),
parsedToken.Header["alg"])
logf(ctx, "Error validating token algorithm: %v", err)
return err
}
// Check if the parsed token is valid...
if !parsedToken.Valid {
logf(ctx, "Token is invalid")
// m.Config.ErrorHandler(ctx, ErrTokenInvalid)
return ErrTokenInvalid
}
if m.Config.Expiration {
if claims, ok := parsedToken.Claims.(jwt.MapClaims); ok {
if expired := claims.VerifyExpiresAt(time.Now().Unix(), true); !expired {
logf(ctx, "Token is expired")
return ErrTokenExpired
}
}
}
logf(ctx, "JWT: %v", parsedToken)
// If we get here, everything worked and we can set the
// user property in context.
ctx.Values().Set(m.Config.ContextKey, parsedToken)
return nil
}