-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathjwt.go
208 lines (167 loc) · 4.36 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
package jwt
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"strings"
)
var (
privateKey = []byte("")
defaultHeader = header{Typ: "JWT", Alg: "HS256"}
registeredClaims = []string{"iss", "sub", "aud", "exp", "nbf", "iat", "jti"}
)
// New returns a token (string) and error. The token is a fully qualified JWT to be sent to a client via HTTP Header or other method. Error returned will be from the newEncoded unexported function.
func New(claims map[string]interface{}) (string, error) {
enc, err := newEncoded(claims)
if err != nil {
return "", err
}
return enc.token, nil
}
// Passes returns a bool indicating whether a token (string) provided has been signed by our server. If true, the client is authenticated and may proceed.
func Passes(token string) bool {
dec, err := newDecoded(token)
if err != nil {
// may want to log some error here so we have visibility
// intentionally simplifying return type to bool for ease
// of use in API. Caller should only do `if auth.Passes(str) {}`
return false
}
signed, err := dec.sign()
if err != nil {
return false
}
return signed.verify(encoded{token: token})
}
// GetClaims() returns a token's claims, allowing
// you to check the values to make sure they match
func GetClaims(token string) map[string]interface{} {
// decode the token
dec, err := newDecoded(token)
if err != nil {
return nil
}
// base64 decode payload
payload, err := base64.RawURLEncoding.DecodeString(dec.payload)
if err != nil {
return nil
}
dst := map[string]interface{}{}
err = json.Unmarshal(payload, &dst)
if err != nil {
return nil
}
return dst
}
// Secret is a helper function to set the unexported privateKey variable used when signing and verifying tokens.
// Its argument is type []byte since we expect users to read this value from a file which can be excluded from source code.
func Secret(key []byte) {
privateKey = key
}
type header struct {
Typ string `json:"typ"`
Alg string `json:"alg"`
}
type payload map[string]interface{}
type encoded struct {
token string
}
type decoded struct {
header string
payload string
}
type signedDecoded struct {
decoded
signature string
}
func newEncoded(claims map[string]interface{}) (encoded, error) {
header, err := json.Marshal(defaultHeader)
if err != nil {
return encoded{}, err
}
for _, claim := range registeredClaims {
if _, ok := claims[claim]; !ok {
claims[claim] = nil
}
}
payload, err := json.Marshal(claims)
if err != nil {
return encoded{}, err
}
d := decoded{header: string(header), payload: string(payload)}
d.encodeInternal()
signed, err := d.sign()
if err != nil {
return encoded{}, err
}
token := signed.token()
e := encoded{token: token}
return e, nil
}
func newDecoded(token string) (decoded, error) {
e := encoded{token: token}
d, err := e.parseToken()
if err != nil {
return d, nil
}
return d, nil
}
func encodeToString(src []byte) string {
return base64.RawURLEncoding.EncodeToString(src)
}
func (d decoded) getHeader() []byte {
return []byte(d.header)
}
func (d decoded) getPayload() []byte {
return []byte(d.payload)
}
func (sd signedDecoded) getSignature() []byte {
return []byte(sd.signature)
}
func (d *decoded) encodeInternal() {
d.header = encodeToString(d.getHeader())
d.payload = encodeToString(d.getPayload())
}
func (d decoded) dot(internals ...string) string {
return strings.Join(internals, ".")
}
func (d *decoded) sign() (signedDecoded, error) {
if d.header == "" || d.payload == "" {
return signedDecoded{}, errors.New("Missing header or payload on Decoded")
}
unsigned := d.dot(d.header, d.payload)
hash := hmac.New(sha256.New, privateKey)
_, err := hash.Write([]byte(unsigned))
if err != nil {
return signedDecoded{}, err
}
signed := signedDecoded{decoded: *d}
signed.signature = encodeToString(hash.Sum(nil))
return signed, nil
}
func (sd signedDecoded) token() string {
return fmt.Sprintf(
"%s.%s.%s",
sd.getHeader(), sd.getPayload(), sd.getSignature(),
)
}
func (sd signedDecoded) verify(enc encoded) bool {
if sd.token() == enc.token {
return true
}
return false
}
func (e encoded) parseToken() (decoded, error) {
parts := strings.Split(e.token, ".")
if len(parts) != 3 {
return decoded{}, errors.New("Error: incorrect # of results from string parsing")
}
d := decoded{
header: parts[0],
payload: parts[1],
}
return d, nil
}