-
-
Notifications
You must be signed in to change notification settings - Fork 6
/
tokenauth.go
208 lines (192 loc) · 6.35 KB
/
tokenauth.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
// Package tokenauth provides jwt token authorisation middleware
// supports HMAC, RSA, ECDSA, RSAPSS EdDSA algorithms
// uses github.com/golang-jwt/jwt/v4 for jwt implementation
//
// Setting Up tokenauth middleware
//
// Using tokenauth with defaults
// app.Use(tokenauth.New(tokenauth.Options{}))
// Specifying Signing method for JWT
// app.Use(tokenauth.New(tokenauth.Options{
// SignMethod: jwt.SigningMethodRS256,
// }))
// By default the Key used is loaded from the JWT_SECRET or JWT_PUBLIC_KEY env variable depending
// on the SigningMethod used. However you can retrive the key from a different source.
// app.Use(tokenauth.New(tokenauth.Options{
// GetKey: func(jwt.SigningMethod) (interface{}, error) {
// // Your Implementation here ...
// },
// }))
// Default authorisation scheme is Bearer, you can specify your own.
// app.Use(tokenauth.New(tokenauth.Options{
// AuthScheme: "Token"
// }))
//
//
// Creating a new token
//
// This can be referred from the underlying JWT package being used https://github.com/golang-jwt/jwt
//
// Example
// claims := jwt.MapClaims{}
// claims["userid"] = "123"
// claims["exp"] = time.Now().Add(time.Minute * 5).Unix()
// // add more claims
// token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// tokenString, err := token.SignedString([]byte(SecretKey))
//
//
// Getting Claims from JWT token from buffalo context
//
// Example of retriving username from claims (this step is same regardless of the signing method used)
// claims := c.Value("claims").(jwt.MapClaims)
// username := claims["username"].(string)
package tokenauth
import (
"errors"
"io/ioutil"
"log"
"net/http"
"github.com/gobuffalo/buffalo"
"github.com/gobuffalo/envy"
"github.com/golang-jwt/jwt/v4"
)
var (
// ErrTokenInvalid is returned when the token provided is invalid
ErrTokenInvalid = errors.New("token invalid")
// ErrNoToken is returned if no token is supplied in the request.
ErrNoToken = errors.New("token not found in request")
// ErrBadSigningMethod is returned if the token sign method in the request
// does not match the signing method used
ErrBadSigningMethod = errors.New("unexpected signing method")
)
// Options for the JWT middleware
type Options struct {
SignMethod jwt.SigningMethod
GetKey func(jwt.SigningMethod) (interface{}, error)
AuthScheme string
}
// New enables jwt token verification if no Sign method is provided,
// by default uses HMAC
func New(options Options) buffalo.MiddlewareFunc {
// set sign method to HMAC if not provided
if options.SignMethod == nil {
options.SignMethod = jwt.SigningMethodHS256
}
if options.GetKey == nil {
options.GetKey = selectGetKeyFunc(options.SignMethod)
}
// get key for validation
key, err := options.GetKey(options.SignMethod)
// if error on getting key exit.
if err != nil {
log.Fatal("couldn't get key:", err)
}
if options.AuthScheme == "" {
options.AuthScheme = "Bearer"
}
return func(next buffalo.Handler) buffalo.Handler {
return func(c buffalo.Context) error {
// get Authorisation header value
authString := c.Request().Header.Get("Authorization")
tokenString, err := getJwtToken(authString, options.AuthScheme)
// if error on getting the token, return with status unauthorized
if err != nil {
return c.Error(http.StatusUnauthorized, err)
}
// validating and parsing the tokenString
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Validating if algorithm used for signing is same as the algorithm in token
if token.Method.Alg() != options.SignMethod.Alg() {
return nil, ErrBadSigningMethod
}
return key, nil
})
// if error validating jwt token, return with status unauthorized
if err != nil {
return c.Error(http.StatusUnauthorized, err)
}
// set the claims as context parameter.
// so that the actions can use the claims from jwt token
c.Set("claims", token.Claims)
// calling next handler
err = next(c)
return err
}
}
}
// selectGetKeyFunc is an helper function to choose the GetKey function
// according to the Signing method used
func selectGetKeyFunc(method jwt.SigningMethod) func(jwt.SigningMethod) (interface{}, error) {
switch method.(type) {
case *jwt.SigningMethodRSA:
return GetKeyRSA
case *jwt.SigningMethodECDSA:
return GetKeyECDSA
case *jwt.SigningMethodRSAPSS:
return GetKeyRSAPSS
case *jwt.SigningMethodEd25519:
return GetkeyEdDSA
default:
return GetHMACKey
}
}
// GetHMACKey gets secret key from env
func GetHMACKey(jwt.SigningMethod) (interface{}, error) {
key, err := envy.MustGet("JWT_SECRET")
return []byte(key), err
}
// GetKeyRSA gets the public key file location from env and returns rsa.PublicKey
func GetKeyRSA(jwt.SigningMethod) (interface{}, error) {
key, err := envy.MustGet("JWT_PUBLIC_KEY")
if err != nil {
return nil, err
}
keyData, err := ioutil.ReadFile(key)
if err != nil {
return nil, err
}
return jwt.ParseRSAPublicKeyFromPEM(keyData)
}
// GetKeyRSAPSS uses GetKeyRSA() since both requires rsa.PublicKey
func GetKeyRSAPSS(signingMethod jwt.SigningMethod) (interface{}, error) {
return GetKeyRSA(signingMethod)
}
// GetKeyECDSA gets the public.pem file location from env and returns ecdsa.PublicKey
func GetKeyECDSA(jwt.SigningMethod) (interface{}, error) {
key, err := envy.MustGet("JWT_PUBLIC_KEY")
if err != nil {
return nil, err
}
keyData, err := ioutil.ReadFile(key)
if err != nil {
return nil, err
}
return jwt.ParseECPublicKeyFromPEM(keyData)
}
// GetKeyECDSA gets the public.pem file location from env and returns eddsa.PublicKey
func GetkeyEdDSA(jwt.SigningMethod) (interface{}, error) {
key, err := envy.MustGet("JWT_PUBLIC_KEY")
if err != nil {
return nil, err
}
keyData, err := ioutil.ReadFile(key)
if err != nil {
return nil, err
}
return jwt.ParseEdPublicKeyFromPEM(keyData)
}
// getJwtToken gets the token from the Authorisation header
// removes the given authorisation scheme part (e.g. Bearer) from the authorisation header value.
// returns No token error if Token is not found
// returns Token Invalid error if the token value cannot be obtained by removing authorisation scheme part (e.g. `Bearer `)
func getJwtToken(authString, authScheme string) (string, error) {
if authString == "" {
return "", ErrNoToken
}
l := len(authScheme)
if len(authString) > l+1 && authString[:l] == authScheme {
return authString[l+1:], nil
}
return "", ErrTokenInvalid
}