-
Notifications
You must be signed in to change notification settings - Fork 19
/
main.go
155 lines (131 loc) · 4.23 KB
/
main.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
package main
import (
"context"
"fmt"
"log"
"net/http"
"time"
"github.com/kataras/jwt"
)
var sharedKey = []byte("sercrethatmaycontainch@r$32chars")
func main() {
http.HandleFunc("/", getTokenHandler)
http.HandleFunc("/protected", verify(protectedHandler))
log.Printf("Server listening on: http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
// Generate token.
func getTokenHandler(w http.ResponseWriter, r *http.Request) {
now := time.Now()
token, err := jwt.Sign(jwt.HS256, sharedKey, map[string]interface{}{
"iat": now.Unix(),
"exp": now.Add(15 * time.Minute).Unix(),
"foo": "bar",
})
if err != nil {
log.Printf("Generate token failure: %v", err)
http.Error(w, "failure: sign and encode the token", http.StatusInternalServerError)
return
}
tokenString := string(token)
w.Header().Set("Content-Type", "text/html;charset=utf-8")
fmt.Fprintf(w, `Token: %s<br/><br/><a href="/protected?token=%s">/protected?token=%s</a>`,
tokenString, tokenString, tokenString)
}
// A route handler that always executed on verified requests.
func protectedHandler(w http.ResponseWriter, r *http.Request) {
verifiedToken := r.Context().Value(tokenContextKey).(*jwt.VerifiedToken)
var claims map[string]interface{}
// ^ can be any type, e.g.
// var claims = struct {
// Foo string `json:"foo"`
// }{}
if err := verifiedToken.Claims(&claims); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
fmt.Fprintf(w, "This is an authenticated request made of token: %q\n\n", verifiedToken.Token)
for key, value := range claims {
fmt.Fprintf(w, "%s = %v (%T)\n", key, value, value)
}
fmt.Fprintf(w, "\nStandard Claims:\n%#+v\n", verifiedToken.StandardClaims)
}
// -----------------------------------|
// Our HTTP middleware implementation |
// -----------------------------------|
type contextKey uint8
const tokenContextKey contextKey = 1
// Our JWT middleware.
// Usage: http.HandleFunc("/route", verify(routeHandler))
// and see the `protectedHandler`.
func verify(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
token := r.URL.Query().Get("token")
if token == "" {
unauthorized(w)
return
}
verifiedToken, err := jwt.Verify(jwt.HS256, sharedKey, []byte(token))
if err != nil {
unauthorized(w)
return
}
// OK, the token is verified.
//
// Store the verified token instance to the context of the Request instance,
// to give the ability to the handler itself decode the custom claims to a custom Go value type.
// If the last is not required, you can store and share
// the map or the go structure value instead of the "verifiedToken" instance (see the 'verify2' example).
r = r.WithContext(context.WithValue(r.Context(), tokenContextKey, verifiedToken))
// Finally, execute the next handler.
next(w, r)
}
}
// Usage:
// http.HandleFunc("/route", verify(routeHandler))
// claims := r.Context().Value(tokenContextKey).(jwt.Map)
func verify2(next http.HandlerFunc) http.HandlerFunc {
/*
Another idea, when you want a single middleware to support different
Go structs (benefit: type safety when access the claims fields):
verify2(getClaimsPtr func() interface{}, next http.HandlerFunc) {
// [...]
claimsPtr := getClaimsPtr()
verifiedToken.Claims(claimsPtr)
// [...]
}
Another idea's usage:
verify2(func() interface{} {
return &userClaims{}
}, routeHandler)
Inside the handler:
claims := r.Context().Value(tokenContextKey).(*userClaims)
*/
return func(w http.ResponseWriter, r *http.Request) {
token := r.URL.Query().Get("token")
if token == "" {
unauthorized(w)
return
}
verifiedToken, err := jwt.Verify(jwt.HS256, sharedKey, []byte(token))
if err != nil {
unauthorized(w)
return
}
var claims jwt.Map
// Another idea:
// claimsPtr := getClaimsFunc()
// verifiedToken.Claims(claimsPtr)
if err = verifiedToken.Claims(&claims); err != nil {
unauthorized(w)
return
}
// Store the the map or the go structure claims value directly.
r = r.WithContext(context.WithValue(r.Context(), tokenContextKey, claims))
// Finally, execute the next handler.
next(w, r)
}
}
func unauthorized(w http.ResponseWriter) {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}