-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathauthentication.go
261 lines (214 loc) · 6.99 KB
/
authentication.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
package apirouter
import (
"net/http"
"strings"
"time"
"github.com/golang-jwt/jwt"
)
const (
defaultExpiration = 1 * time.Hour
// AuthorizationHeader is the auth header
AuthorizationHeader = "Authorization"
// AuthorizationBearer is the second part of the auth header
AuthorizationBearer = "Bearer"
// CookieName is for the secure cookie that also has the JWT token
CookieName = "jwt_token"
)
// Claims is our custom JWT claims
type Claims struct {
jwt.StandardClaims // The standard JWT fields
UserID string `json:"user_id"` // The user ID set on the claims
}
// CreateToken will make a token from claims
func (c Claims) CreateToken(expiration time.Duration, sessionSecret string) (string, error) {
// Create a new token object, specifying signing method, and the claims
token := jwt.NewWithClaims(jwt.SigningMethodHS256, createClaims(c.UserID, c.Issuer, c.Id, expiration))
// Sign and get the complete encoded token as a string using the secret
return token.SignedString([]byte(sessionSecret))
}
// Verify will check the claims against known verifications
func (c Claims) Verify(issuer string) (bool, error) {
// Invalid issuer
if c.Issuer != issuer {
return false, ErrIssuerMismatch
}
// Valid Session ID
if len(c.Id) == 0 {
return false, ErrInvalidSessionID
}
// Valid User ID
if c.UserID == "" {
return false, ErrInvalidUserID
}
return true, nil
}
// IsEmpty will detect if the claims are empty or not
func (c Claims) IsEmpty() bool {
return len(c.UserID) <= 0
}
// createClaims will make a new set of claims for JWT
func createClaims(userID, issuer, sessionID string, expiration time.Duration) Claims {
// Set default if not set
if expiration <= 0 {
expiration = defaultExpiration
}
return Claims{
jwt.StandardClaims{
ExpiresAt: time.Now().Add(expiration).UTC().Unix(),
Id: sessionID,
IssuedAt: time.Now().UTC().Unix(),
Issuer: issuer,
NotBefore: time.Now().UTC().Unix(),
},
userID,
}
}
// CreateToken will make the claims, and then make/sign the token
func CreateToken(sessionSecret, userID, issuer, sessionID string,
expiration time.Duration) (string, error) {
// Create a new token object, specifying signing method, and the claims
token := jwt.NewWithClaims(jwt.SigningMethodHS256, createClaims(userID, issuer, sessionID, expiration))
// Sign and get the complete encoded token as a string using the secret
return token.SignedString([]byte(sessionSecret))
}
// ClearToken will remove the token from the response and request
func ClearToken(w http.ResponseWriter, req *http.Request) {
// Remove from response
w.Header().Del(AuthorizationHeader)
// Create empty cookie
cookie := &http.Cookie{
Path: "/",
Name: CookieName,
Value: "",
Expires: time.Now().Add(-24 * time.Hour),
}
// Remove from request
if req != nil && req.Header != nil {
req.Header.Del(AuthorizationHeader)
req.Header.Del("Cookie") // Remove all cookies
req.AddCookie(cookie) // Add the empty cookie
}
// Clear any cookie out
http.SetCookie(w, cookie)
}
// Check will check if the JWT is present and valid in the request and then extend the token
func Check(w http.ResponseWriter, r *http.Request, sessionSecret, issuer string,
sessionAge time.Duration) (authenticated bool, req *http.Request, err error) {
var jwtToken string
// Look for a cookie value first
var cookie *http.Cookie
cookie, _ = r.Cookie(CookieName)
if cookie != nil {
jwtToken = cookie.Value
} else { // Get from the auth header
authHeaderValue := r.Header.Get(AuthorizationHeader)
authHeader := strings.Split(authHeaderValue, AuthorizationBearer+" ")
if len(authHeader) != 2 {
err = ErrHeaderInvalid
return
}
// Set the token value
jwtToken = authHeader[1]
}
// Parse the JWT token
var token *jwt.Token
if token, err = jwt.ParseWithClaims(jwtToken, &Claims{}, func(_ *jwt.Token) (interface{}, error) {
// This error NEVER occurs since we are using our own signing method
/*if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
// return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
return nil, ErrUnexpectedSigningMethod
}*/
return []byte(sessionSecret), nil
}); err != nil {
return
}
// This NEVER occurs, so far all errors have been returned first so the above case would get hit
/* else if token == nil {
// err = fmt.Errorf("token was nil from: %s", jwtToken)
err = ErrTokenNil
return
}*/
// Check we have claims and validity of token
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
// Now verify the claims are good
if _, claimErr := claims.Verify(issuer); claimErr != nil {
// err = fmt.Errorf("claims failed validation: %w", claimErr)
err = ErrClaimsValidationFailed
return
}
// This case is NEVER hit since there is always a corresponding error
/* else if !verified {
err = ErrClaimsValidationFailed
return
}*/
// Create new token
var newToken string
if newToken, err = CreateToken(
sessionSecret,
claims.UserID,
issuer,
claims.Id,
sessionAge,
); err != nil {
return
}
// Set the token in the writer (response)
SetTokenHeader(w, r, newToken, sessionAge)
// Add the claims to the request for future use in router actions
req = SetCustomData(r, claims)
authenticated = true
} else { // Not sure how or why this error would be triggered...
err = ErrJWTInvalid
}
return
}
// GetClaims will return the current claims from the request
func GetClaims(req *http.Request) Claims {
if claims := GetCustomData(req); claims != nil {
return *claims.(*Claims)
}
return Claims{}
}
// GetTokenFromHeaderFromRequest will get the token value from the request
func GetTokenFromHeaderFromRequest(req *http.Request) string {
headerVal := req.Header.Get(AuthorizationHeader)
if parts := strings.Split(headerVal, " "); len(parts) > 1 {
return parts[1]
}
return ""
}
// GetTokenFromHeader will get the token value from the header
func GetTokenFromHeader(w http.ResponseWriter) string {
headerVal := w.Header().Get(AuthorizationHeader)
if parts := strings.Split(headerVal, " "); len(parts) > 1 {
return parts[1]
}
return ""
}
// GetTokenFromResponse will get the token value from the HTTP response
func GetTokenFromResponse(res *http.Response) string {
headerVal := res.Header.Get(AuthorizationHeader)
if parts := strings.Split(headerVal, " "); len(parts) > 1 {
return parts[1]
}
return ""
}
// SetTokenHeader will set the authentication token on the response and set a cookie
func SetTokenHeader(w http.ResponseWriter, r *http.Request, token string, expiration time.Duration) {
// Set on the response
w.Header().Set(AuthorizationHeader, AuthorizationBearer+" "+token)
// Set on the request
r.Header.Set(AuthorizationHeader, AuthorizationBearer+" "+token)
// Create the cookie
cookie := &http.Cookie{
Path: "/",
Name: CookieName,
Value: token,
Expires: time.Now().UTC().Add(expiration),
// todo: secure / http only etc
}
// Set the cookie on the request
r.AddCookie(cookie)
// Set the cookie (response)
http.SetCookie(w, cookie)
}