1 package jwt_test
2
3 import (
4 "context"
5 "errors"
6 "testing"
7 "time"
8
9 "github.com/lestrrat-go/jwx/internal/json"
10 "github.com/lestrrat-go/jwx/jwt"
11 "github.com/stretchr/testify/assert"
12 )
13
14 func TestGHIssue10(t *testing.T) {
15 t.Parallel()
16
17
18 testcases := []struct {
19 ClaimName string
20 ClaimValue string
21 OptionFunc func(string) jwt.ValidateOption
22 BuildFunc func(v string) (jwt.Token, error)
23 }{
24 {
25 ClaimName: jwt.IssuerKey,
26 ClaimValue: `github.com/lestrrat-go/jwx`,
27 OptionFunc: jwt.WithIssuer,
28 BuildFunc: func(v string) (jwt.Token, error) {
29 return jwt.NewBuilder().
30 Issuer(v).
31 Build()
32 },
33 },
34 {
35 ClaimName: jwt.JwtIDKey,
36 ClaimValue: `my-sepcial-key`,
37 OptionFunc: jwt.WithJwtID,
38 BuildFunc: func(v string) (jwt.Token, error) {
39 return jwt.NewBuilder().
40 JwtID(v).
41 Build()
42 },
43 },
44 {
45 ClaimName: jwt.SubjectKey,
46 ClaimValue: `very important subject`,
47 OptionFunc: jwt.WithSubject,
48 BuildFunc: func(v string) (jwt.Token, error) {
49 return jwt.NewBuilder().
50 Subject(v).
51 Build()
52 },
53 },
54 }
55 for _, tc := range testcases {
56 tc := tc
57 t.Run(tc.ClaimName, func(t *testing.T) {
58 t.Parallel()
59 t1, err := tc.BuildFunc(tc.ClaimValue)
60 if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
61 return
62 }
63
64
65
66 if !assert.NoError(t, jwt.Validate(t1), "t1.Validate should succeed") {
67 return
68 }
69
70
71 if !assert.NoError(t, jwt.Validate(t1, tc.OptionFunc(tc.ClaimValue)), "t1.Validate should succeed") {
72 return
73 }
74
75 if !assert.Error(t, jwt.Validate(t1, jwt.WithIssuer("poop")), "t1.Validate should fail") {
76 return
77 }
78 })
79 }
80 t.Run(jwt.IssuedAtKey, func(t *testing.T) {
81 t.Parallel()
82 t1 := jwt.New()
83 t1.Set(jwt.IssuedAtKey, time.Now().Add(365*24*time.Second))
84
85 t.Run(`iat too far in the past`, func(t *testing.T) {
86 err := jwt.Validate(t1)
87 if !assert.Error(t, err, `jwt.Validate should fail`) {
88 return
89 }
90
91 if !assert.True(t, errors.Is(err, jwt.ErrInvalidIssuedAt()), `error should be jwt.ErrInvalidIssuedAt`) {
92 return
93 }
94
95 if !assert.False(t, errors.Is(err, jwt.ErrTokenNotYetValid()), `error should be not ErrNotYetValid`) {
96 return
97 }
98
99 if !assert.True(t, jwt.IsValidationError(err), `error should be a validation error`) {
100 return
101 }
102 })
103 })
104 t.Run(jwt.AudienceKey, func(t *testing.T) {
105 t.Parallel()
106 t1, err := jwt.NewBuilder().
107 Claim(jwt.AudienceKey, []string{"foo", "bar", "baz"}).
108 Build()
109 if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
110 return
111 }
112
113
114
115 t.Run("`aud` check disabled", func(t *testing.T) {
116 t.Parallel()
117 if !assert.NoError(t, jwt.Validate(t1), `jwt.Validate should succeed`) {
118 return
119 }
120 })
121
122
123
124 t.Run("`aud` contains `baz`", func(t *testing.T) {
125 t.Parallel()
126 if !assert.NoError(t, jwt.Validate(t1, jwt.WithAudience("baz")), "jwt.Validate should succeed") {
127 return
128 }
129 })
130
131 t.Run("check `aud` contains `poop`", func(t *testing.T) {
132 t.Parallel()
133 err := jwt.Validate(t1, jwt.WithAudience("poop"))
134 if !assert.Error(t, err, "token.Validate should fail") {
135 return
136 }
137 if !assert.False(t, errors.Is(err, jwt.ErrTokenNotYetValid()), `error should be not ErrNotYetValid`) {
138 return
139 }
140 if !assert.True(t, jwt.IsValidationError(err), `error should be a validation error`) {
141 return
142 }
143 })
144 })
145 t.Run(jwt.SubjectKey, func(t *testing.T) {
146 t.Parallel()
147 t1, err := jwt.NewBuilder().
148 Claim(jwt.SubjectKey, "github.com/lestrrat-go/jwx").
149 Build()
150 if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
151 return
152 }
153
154
155
156 if !assert.NoError(t, jwt.Validate(t1), "token.Validate should succeed") {
157 return
158 }
159
160
161 if !assert.NoError(t, jwt.Validate(t1, jwt.WithSubject(t1.Subject())), "token.Validate should succeed") {
162 return
163 }
164
165 if !assert.Error(t, jwt.Validate(t1, jwt.WithSubject("poop")), "token.Validate should fail") {
166 return
167 }
168 })
169 t.Run(jwt.NotBeforeKey, func(t *testing.T) {
170 t.Parallel()
171
172
173 tm := time.Now().Add(72 * time.Hour)
174
175 t1, err := jwt.NewBuilder().
176 Claim(jwt.NotBeforeKey, tm).
177 Build()
178 if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
179 return
180 }
181
182 testcases := []struct {
183 Name string
184 Options []jwt.ValidateOption
185 Error bool
186 }{
187 {
188 Name: `'nbf' is less than current time`,
189 Error: true,
190 },
191 {
192 Name: `skew is large enough`,
193 Options: []jwt.ValidateOption{
194 jwt.WithAcceptableSkew(73 * time.Hour),
195 },
196 },
197 {
198
199 Name: `clock is set to after time in nbf`,
200 Options: []jwt.ValidateOption{
201 jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(time.Hour) })),
202 },
203 },
204 {
205
206 Name: `clock is set to the same time as nbf`,
207 Options: []jwt.ValidateOption{
208 jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm })),
209 },
210 },
211 }
212 for _, tc := range testcases {
213 tc := tc
214 t.Run(tc.Name, func(t *testing.T) {
215 if tc.Error {
216 err := jwt.Validate(t1, tc.Options...)
217 if !assert.Error(t, err, "token.Validate should fail") {
218 return
219 }
220 if !assert.True(t, errors.Is(err, jwt.ErrTokenNotYetValid()), `error should be ErrTokenNotYetValid`) {
221 return
222 }
223 if !assert.False(t, errors.Is(err, jwt.ErrTokenExpired()), `error should not be ErrTokenExpierd`) {
224 return
225 }
226 if !assert.True(t, jwt.IsValidationError(err), `error should be a validation error`) {
227 return
228 }
229 } else {
230 if !assert.NoError(t, jwt.Validate(t1, tc.Options...), "token.Validate should succeed") {
231 return
232 }
233 }
234 })
235 }
236 })
237 t.Run(jwt.ExpirationKey, func(t *testing.T) {
238 t.Parallel()
239
240 tm := time.Now()
241 t1, err := jwt.NewBuilder().
242
243 Claim(jwt.IssuedAtKey, tm.Add(-1*time.Hour)).
244
245 Claim(jwt.ExpirationKey, tm.Add(-58*time.Minute)).
246 Build()
247 if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
248 return
249 }
250
251
252 t.Run("exp set in the past", func(t *testing.T) {
253 t.Parallel()
254 err := jwt.Validate(t1)
255 if !assert.Error(t, err, "token.Validate should fail") {
256 return
257 }
258 if !assert.True(t, errors.Is(err, jwt.ErrTokenExpired()), `error should be ErrTokenExpired`) {
259 return
260 }
261 if !assert.False(t, errors.Is(err, jwt.ErrTokenNotYetValid()), `error should be not ErrNotYetValid`) {
262 return
263 }
264 if !assert.True(t, jwt.IsValidationError(err), `error should be a validation error`) {
265 return
266 }
267 })
268
269
270 t.Run("exp is set in the past, but acceptable skew is large", func(t *testing.T) {
271 t.Parallel()
272 if !assert.NoError(t, jwt.Validate(t1, jwt.WithAcceptableSkew(time.Hour)), "token.Validate should succeed (1)") {
273 return
274 }
275 })
276
277
278
279 t.Run("exp is set in the past, but clock is also set in the past", func(t *testing.T) {
280 t.Parallel()
281 clock := jwt.ClockFunc(func() time.Time {
282 return tm.Add(-59 * time.Minute)
283 })
284 if !assert.NoError(t, jwt.Validate(t1, jwt.WithClock(clock)), "token.Validate should succeed (2)") {
285 return
286 }
287 })
288 })
289 t.Run("Unix zero times", func(t *testing.T) {
290 t.Parallel()
291 tm := time.Unix(0, 0)
292 t1, err := jwt.NewBuilder().
293 Claim(jwt.NotBeforeKey, tm).
294 Claim(jwt.IssuedAtKey, tm).
295 Claim(jwt.ExpirationKey, tm).
296 Build()
297 if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
298 return
299 }
300
301
302 if assert.NoError(t, jwt.Validate(t1), "token.Validate should pass") {
303 return
304 }
305 })
306 t.Run("Go zero times", func(t *testing.T) {
307 t.Parallel()
308 tm := time.Time{}
309 t1, err := jwt.NewBuilder().
310 Claim(jwt.NotBeforeKey, tm).
311 Claim(jwt.IssuedAtKey, tm).
312 Claim(jwt.ExpirationKey, tm).
313 Build()
314 if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
315 return
316 }
317
318
319 if assert.NoError(t, jwt.Validate(t1), "token.Validate should pass") {
320 return
321 }
322 })
323 t.Run("Parse and validate", func(t *testing.T) {
324 t.Parallel()
325 tm := time.Now()
326 t1, err := jwt.NewBuilder().
327
328 Claim(jwt.IssuedAtKey, tm.Add(-1*time.Hour)).
329
330 Claim(jwt.ExpirationKey, tm.Add(-58*time.Minute)).
331 Build()
332 if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
333 return
334 }
335
336 buf, err := json.Marshal(t1)
337 if !assert.NoError(t, err, `json.Marshal should succeed`) {
338 return
339 }
340
341 _, err = jwt.Parse(buf, jwt.WithValidate(true))
342
343 if !assert.Error(t, err, "jwt.Parse should fail") {
344 return
345 }
346
347 _, err = jwt.Parse(buf, jwt.WithValidate(true), jwt.WithAcceptableSkew(time.Hour))
348
349
350 if !assert.NoError(t, err, "jwt.Parse should succeed (1)") {
351 return
352 }
353
354
355
356 clock := jwt.ClockFunc(func() time.Time {
357 return tm.Add(-59 * time.Minute)
358 })
359 _, err = jwt.Parse(buf, jwt.WithValidate(true), jwt.WithClock(clock))
360 if !assert.NoError(t, err, "jwt.Parse should succeed (2)") {
361 return
362 }
363 })
364 t.Run("any claim value", func(t *testing.T) {
365 t.Parallel()
366 t1, err := jwt.NewBuilder().
367 Claim("email", "email@example.com").
368 Build()
369 if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
370 return
371 }
372
373
374
375 if !assert.NoError(t, jwt.Validate(t1), "t1.Validate should succeed") {
376 return
377 }
378
379
380 if !assert.NoError(t, jwt.Validate(t1, jwt.WithClaimValue("email", "email@example.com")), "t1.Validate should succeed") {
381 return
382 }
383
384 if !assert.Error(t, jwt.Validate(t1, jwt.WithClaimValue("email", "poop")), "t1.Validate should fail") {
385 return
386 }
387 if !assert.Error(t, jwt.Validate(t1, jwt.WithClaimValue("xxxx", "email@example.com")), "t1.Validate should fail") {
388 return
389 }
390 if !assert.Error(t, jwt.Validate(t1, jwt.WithClaimValue("xxxx", "")), "t1.Validate should fail") {
391 return
392 }
393 })
394 }
395
396 func TestClaimValidator(t *testing.T) {
397 t.Parallel()
398 const myClaim = "my-claim"
399 err0 := errors.New(myClaim + " does not exist")
400 v := jwt.ValidatorFunc(func(_ context.Context, tok jwt.Token) error {
401 _, ok := tok.Get(myClaim)
402 if !ok {
403 return err0
404 }
405 return nil
406 })
407
408 testcases := []struct {
409 Name string
410 MakeToken func() jwt.Token
411 Error error
412 }{
413 {
414 Name: "Successful validation",
415 MakeToken: func() jwt.Token {
416 t1 := jwt.New()
417 _ = t1.Set(myClaim, map[string]interface{}{"k": "v"})
418 return t1
419 },
420 },
421 {
422 Name: "Target claim does not exist",
423 MakeToken: func() jwt.Token {
424 t1 := jwt.New()
425 _ = t1.Set("other-claim", map[string]interface{}{"k": "v"})
426 return t1
427 },
428 Error: err0,
429 },
430 }
431 for _, tc := range testcases {
432 tc := tc
433 t.Run(tc.Name, func(t *testing.T) {
434 t.Parallel()
435 t1 := tc.MakeToken()
436 if err := tc.Error; err != nil {
437 if !assert.ErrorIs(t, jwt.Validate(t1, jwt.WithValidator(v)), err) {
438 return
439 }
440 return
441 }
442
443 if !assert.NoError(t, jwt.Validate(t1, jwt.WithValidator(v))) {
444 return
445 }
446 })
447 }
448 }
449
View as plain text