-
Notifications
You must be signed in to change notification settings - Fork 1
/
jwit_test.go
298 lines (243 loc) · 9.55 KB
/
jwit_test.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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
package jwit_test
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"fmt"
"io/ioutil"
"net/http"
"path"
"time"
"github.com/gilbsgilbs/jwit"
)
// This is a simple example of how to sign a JWT from a JWKS containing private keys.
func Example_signJWT() {
// Create a signer from a JSON Web Key Set (JWKS) containing private keys.
// The JWKS payload will typically reside your authorization server's config or in a secure vault.
signer, _ := jwit.NewSigner([]byte(`{"keys": [ ... private JSON Web Keys ... ]}`))
// You can optionnaly assign default claims to this signer.
signer.DefaultClaims.Issuer = "My Authorization Server"
signer.DefaultClaims.Duration = 1 * time.Hour // Shorthand for "Expiry: time.Now().Add(1 * time.Hour)"
// Create a JWT that expires in one hour
rawJWT, _ := signer.SignJWT(jwit.C{
// You can optionnaly override default claims here
// Duration: 1 * time.Hour,
})
// And that's all!
fmt.Println("JWT:", rawJWT)
}
// This example shows how to sign a JWT with private claims .
func Example_signJWTWithPrivateClaims() {
type MyCustomClaims struct {
Payload string `json:"payload"`
IsAdmin bool `json:"is_admin"`
}
signer, _ := jwit.NewSigner([]byte(`{"keys": [ ... ]}`))
signer.DefaultClaims.Duration = 1 * time.Hour
// Create a JWT with two custom claims
rawJWT, _ := signer.SignJWT(
jwit.C{},
MyCustomClaims{
Payload: "custom data",
IsAdmin: false,
},
)
// Done!
fmt.Println("JWT:", rawJWT)
}
func Example_verifyJWT() {
var rsaPublicKey *rsa.PublicKey
var ecdsaPublicKey *ecdsa.PublicKey
// Create a verifier
verifier, _ := jwit.NewVerifier(
// Recommended: specify an URL to the issuer's public JWKS.
// this will allow JWIT to catch changes to the JWKS.
&jwit.Issuer{
// This should correspond to the "iss" claims of the JWTs
Name: "myVeryOwnIssuer",
// This is an HTTP(S) URL where the authorization server publishes its public keys.
// It will be queried the first time a JWT is verified and then periodically.
JWKSURL: "https://my-very-own-issuer.com/.well-known/jwks.json",
// You can specify how long the issuer's public keys should be kept in cache.
// Passed that delay, the JWKS will be re-fetched once asynchronously.
// Defaults to 24 hours.
TTL: 10 * time.Hour,
},
// Alternatively: pass in public keys directly and disable remote JWKS.
&jwit.Issuer{
Name: "myOtherIssuer",
PublicKeys: []interface{}{
// using Go's crypto types
rsaPublicKey,
ecdsaPublicKey,
// or using a marshalled JWKS JSON
[]byte(`{"keys": [ … your JWKS … ]}`),
// or using marshalled PEM blocks
[]byte(`-----BEGIN RSA PUBLIC KEY----- ... -----END RSA PUBLIC KEY-----`),
},
},
// ... you can specify as many issuers as you want
)
// You typically get this from a Cookie or Authorization header.
rawJWT := "ey[...]pX.ey[...]DI.Sf[...]5c"
// Verify the JWT using its "iss" claim
isValid, _ := verifier.VerifyJWT(rawJWT)
if isValid {
// do stuff
}
}
// This example shows how to unmarshal private claims from a JWT.
func Example_verifyJWTUnmarshalPrivateClaims() {
type MyCustomClaims struct {
Payload string `json:"payload"`
IsAdmin bool `json:"is_admin"`
}
verifier, _ := jwit.NewVerifier()
rawJWT := "ey[...]pX.ey[...]DI.Sf[...]5c"
var myCustomClaims MyCustomClaims
// if the JWT is valid, the claims will be unmarshaled into myCustomClaims
isValid, _ := verifier.VerifyJWT(rawJWT, &myCustomClaims)
if isValid {
// do stuff
}
}
// Verifying a JWT against a specific set of keys can sometimes be useful (for example if the JWT
// issuer doesn't provide an "iss" claim or if you don't know the issuer's public key in advance).
// This example demonstrates how you can validate a JWT using your own set of public keys on an
// existing verifier.
func Example_verifyJWTWithGoCryptoKeys() {
var someECDSAPublicKey *ecdsa.PublicKey
var someRSAPublicKey *rsa.PublicKey
// Create a new empty verifyer
verifier, _ := jwit.NewVerifier()
// You'll typically get this from a Cookie or Authorization header.
rawJWT := "ey[...]pX.ey[...]DI.Sf[...]5c"
// If any of the RSA or ECDSA key was used to sign the JWT, isValid will be set to true.
isValid, _ := verifier.VerifyJWTWithKeys(
rawJWT,
[]crypto.PublicKey{someECDSAPublicKey, someRSAPublicKey},
)
if isValid {
// do stuff
}
}
// This example shows how you can expose your public JWKS to the world.
func Example_exposeJWKS() {
// All JWKS listed in NewSigner() will be merged into one JWKS when calling "DumpPublicJWKS()".
signer, _ := jwit.NewSigner(
// The first argument must contain your (private) signing keys. If it contains more than one
// signing keys, one will be picked at random each time you call `signJWT()`.
[]byte(`{"keys": [ ... some private JSON Web Keys ... ]}`),
// Following (variadic) arguments are optional and can be private or public keys.
//
// They won't be used to sign your JWTs, but will show as public keys in "DumpPublicJWKS()".
// This is useful when you want to renew your signing keys as you need to make sure resource servers
// still consider previously created tokens as valid (until they expire). Consequently, you'll usually
// want to set these to your "old" signing keys, and eventually remove them.
//
// Refer to the example dedicated to signing keys renewal for details.
[]byte(`{"keys": [ ... some JSON Web Keys ... ]}`),
[]byte(`{"keys": [ ... other JSON Web Keys ... ]}`),
[]byte(`{"keys": [ ... and so on ... ]}`),
)
privateJWKS, _ := signer.DumpSigningJWKS()
fmt.Println("my private signing JWKS (to keep secret): ", string(privateJWKS))
otherJWKS, _ := signer.DumpOtherJWKS()
fmt.Println("my other JWKS (to keep secret):", string(otherJWKS))
http.HandleFunc(
"/.well-known/jwks.json",
func(w http.ResponseWriter, req *http.Request) {
// This function exposes all the keys as public keys.
jwks, err := signer.DumpPublicJWKS()
if err != nil {
panic(err)
}
_, _ = w.Write(jwks)
},
)
_ = http.ListenAndServe(":8080", nil)
}
// This shows how you can create a new signer using a PEM file as signing keys. Note however
// that it is recommended you used JWKS instead.
func Example_signerFromPEM() {
// Read a standard PEM file that may contain multiple private keys.
// If the file contains multiple keys, one will be picked at random each time you sign a token.
pemBytes, _ := ioutil.ReadFile(path.Join("myPrivateKeys.pem"))
// Signer will detect PEM data vs JWKS data, so you can just do:
signer, err := jwit.NewSigner(pemBytes)
// Or if you prefer being explicit:
// signer, err := jwit.NewSignerFromPEM(pemBytes)
if err != nil {
panic(err)
}
// you can then use your signer normally
rawJWT, _ := signer.SignJWT(jwit.C{})
// and serve this token.
fmt.Println(rawJWT)
}
// This example shows how to create a new signer using private keys from go's crypto package.
func Example_signerFromGoCrypto() {
var ecdsaPrivateKey *ecdsa.PrivateKey
var rsaPrivateKey *rsa.PrivateKey
// Just create the signer
signer, err := jwit.NewSignerFromCryptoKeys(
[]crypto.PrivateKey{
ecdsaPrivateKey,
rsaPrivateKey,
// ... and so on
},
)
if err != nil {
panic(err)
}
// you can then use your signer normally
rawJWT, _ := signer.SignJWT(jwit.C{})
// and serve this token.
fmt.Println(rawJWT)
}
// This example explains step-by-step how to gracefully renew signing keys.
func Example_signerGracefullyRenewSigningKeys() {
// Let's say your authorization server uses this signer:
signer, _ := jwit.NewSigner(
[]byte(`{"keys": [ ... old signing keys ... ]}`),
)
// Your authorization server exposes the public keys corresponding to this signer
// at "/.well-known/jwks.json". Your resource servers use this public JWKS (tied to your
// old signing keys) to verify the JWTs:
publicJwks, _ := signer.DumpPublicJWKS()
fmt.Println("/.well-known/jwks.json => ", string(publicJwks))
// Now, you can safely replace your signer with this one:
signer, _ = jwit.NewSigner(
[]byte(`{"keys": [ ... old signing keys ... ]}`),
[]byte(`{"keys": [ ... new signing keys ... ]}`),
)
// this signer will still sign the JWTs with the same keys as before, but declare the new
// public signing keys at /.well-known/jwks.json.
// So this should list the old signing keys along with the new ones.
publicJwks, _ = signer.DumpPublicJWKS()
fmt.Println("/.well-known/jwks.json => ", string(publicJwks))
// You then need to wait for the new public keys to propagate across all your resource servers.
// How long you need to wait depends on the TTL each resource server has defined.
// ...
// Once all resource servers have refreshed the JWKS, you can sign the JWTs with your new keys:
signer, _ = jwit.NewSigner(
[]byte(`{"keys": [ ... new signing keys ... ]}`),
[]byte(`{"keys": [ ... old signing keys ... ]}`),
)
// This new signer will sign the JWTs with the new signing keys, but keep the old keys declared
// in the public JWKS.
// So this should list should not have changed compared to the previous time:
publicJwks, _ = signer.DumpPublicJWKS()
fmt.Println("/.well-known/jwks.json => ", string(publicJwks))
// Now you need to wait again for all your tokens signed with the old signing keys to expire.
// How long you need to wait depends on the value of the "exp" claim for each token.
// ...
// Finally, you can revoke the old signing keys:
signer, _ = jwit.NewSigner(
[]byte(`{"keys": [ ... new signing keys ... ]}`),
)
// And this will only list the new public signing keys:
publicJwks, _ = signer.DumpPublicJWKS()
fmt.Println("/.well-known/jwks.json => ", string(publicJwks))
// 👏👏👏👏👏👏👏👏👏👏👏👏👏
}