1
16
17 package jwtverifier
18
19 import (
20 "bytes"
21 "encoding/json"
22 "io/ioutil"
23 "log"
24 "net/http"
25 "net/url"
26 "os"
27 "reflect"
28 "strings"
29 "testing"
30 "time"
31
32 "github.com/okta/okta-jwt-verifier-golang/adaptors/lestrratGoJwx"
33 "github.com/okta/okta-jwt-verifier-golang/discovery/oidc"
34 "github.com/okta/okta-jwt-verifier-golang/utils"
35 )
36
37 func Test_the_verifier_defaults_to_oidc_if_nothing_is_provided_for_discovery(t *testing.T) {
38 jvs := JwtVerifier{
39 Issuer: "issuer",
40 }
41
42 jv := jvs.New()
43
44 if reflect.TypeOf(jv.GetDiscovery()) != reflect.TypeOf(oidc.Oidc{}) {
45 t.Errorf("discovery did not set to oidc by default. Was set to: %s",
46 reflect.TypeOf(jv.GetDiscovery()))
47 }
48 }
49
50 func Test_the_verifier_defaults_to_lestrratGoJwx_if_nothing_is_provided_for_adaptor(t *testing.T) {
51 jvs := JwtVerifier{
52 Issuer: "issuer",
53 }
54
55 jv := jvs.New()
56
57 if reflect.TypeOf(jv.GetAdaptor()) != reflect.TypeOf(&lestrratGoJwx.LestrratGoJwx{}) {
58 t.Errorf("adaptor did not set to lestrratGoJwx by default. Was set to: %s",
59 reflect.TypeOf(jv.GetAdaptor()))
60 }
61 }
62
63 func Test_can_validate_iss_from_issuer_provided(t *testing.T) {
64 jvs := JwtVerifier{
65 Issuer: "https://golang.oktapreview.com",
66 }
67
68 jv := jvs.New()
69
70 err := jv.validateIss("test")
71 if err == nil {
72 t.Errorf("the issuer validation did not trigger an error")
73 }
74 }
75
76 func Test_can_validate_nonce(t *testing.T) {
77 tv := map[string]string{}
78 tv["nonce"] = "abc123"
79
80 jvs := JwtVerifier{
81 Issuer: "https://golang.oktapreview.com",
82 ClaimsToValidate: tv,
83 }
84
85 jv := jvs.New()
86
87 err := jv.validateNonce("test")
88 if err == nil {
89 t.Errorf("the nonce validation did not trigger an error")
90 }
91 }
92
93 func Test_can_validate_aud(t *testing.T) {
94 tv := map[string]string{}
95 tv["aud"] = "abc123"
96
97 jvs := JwtVerifier{
98 Issuer: "https://golang.oktapreview.com",
99 ClaimsToValidate: tv,
100 }
101
102 jv := jvs.New()
103
104 err := jv.validateAudience("test")
105 if err == nil {
106 t.Errorf("the audience validation did not trigger an error")
107 }
108 }
109
110 func Test_can_validate_cid(t *testing.T) {
111 tv := map[string]string{}
112 tv["cid"] = "abc123"
113
114 jvs := JwtVerifier{
115 Issuer: "https://golang.oktapreview.com",
116 ClaimsToValidate: tv,
117 }
118
119 jv := jvs.New()
120
121 err := jv.validateClientId("test")
122 if err == nil {
123 t.Errorf("the cid validation did not trigger an error")
124 }
125 }
126
127 func Test_can_validate_iat(t *testing.T) {
128 jvs := JwtVerifier{
129 Issuer: "https://golang.oktapreview.com",
130 }
131
132 jv := jvs.New()
133
134
135 err := jv.validateIat(float64(time.Now().Unix() + 300))
136 if err == nil {
137 t.Errorf("the iat validation did not trigger an error")
138 }
139
140
141 err = jv.validateIat(float64(time.Now().Unix()))
142 if err != nil {
143 t.Errorf("the iat validation triggered an error")
144 }
145 }
146
147 func Test_can_validate_exp(t *testing.T) {
148 jvs := JwtVerifier{
149 Issuer: "https://golang.oktapreview.com",
150 }
151
152 jv := jvs.New()
153
154
155 err := jv.validateExp(float64(time.Now().Unix() - 300))
156 if err == nil {
157 t.Errorf("the exp validation did not trigger an error for expired token")
158 }
159
160
161 err = jv.validateExp(float64(time.Now().Unix()))
162 if err != nil {
163 t.Errorf("the exp validation triggered an error for valid token")
164 }
165 }
166
167
168 func Test_invalid_formatting_of_id_token_throws_an_error(t *testing.T) {
169 jvs := JwtVerifier{
170 Issuer: "https://golang.oktapreview.com",
171 }
172
173 jv := jvs.New()
174
175 _, err := jv.VerifyIdToken("aa")
176
177 if err == nil {
178 t.Errorf("an error was not thrown when an id token does not contain at least 1 period ('.')")
179 }
180
181 if !strings.Contains(err.Error(), "token must contain at least 1 period ('.')") {
182 t.Errorf("the error for id token with no periods did not trigger")
183 }
184 }
185
186 func Test_an_id_token_header_that_is_improperly_formatted_throws_an_error(t *testing.T) {
187 jvs := JwtVerifier{
188 Issuer: "https://golang.oktapreview.com",
189 }
190
191 jv := jvs.New()
192
193 _, err := jv.VerifyIdToken("123456789.aa.aa")
194
195 if !strings.Contains(err.Error(), "does not appear to be a base64 encoded string") {
196 t.Errorf("the error for id token with header that is not base64 encoded did not trigger")
197 }
198 }
199
200 func Test_an_id_token_header_that_is_not_decoded_into_json_throws_an_error(t *testing.T) {
201 jvs := JwtVerifier{
202 Issuer: "https://golang.oktapreview.com",
203 }
204
205 jv := jvs.New()
206
207 _, err := jv.VerifyIdToken("aa.aa.aa")
208
209 if !strings.Contains(err.Error(), "not a json object") {
210 t.Errorf("the error for id token with header that is not a json object did not trigger")
211 }
212 }
213
214 func Test_an_id_token_header_that_is_not_contain_the_correct_parts_throws_an_error(t *testing.T) {
215 jvs := JwtVerifier{
216 Issuer: "https://golang.oktapreview.com",
217 }
218
219 jv := jvs.New()
220
221 _, err := jv.VerifyIdToken("ew0KICAia2lkIjogImFiYzEyMyIsDQogICJhbmQiOiAidGhpcyINCn0.aa.aa")
222
223 if !strings.Contains(err.Error(), "header must contain an 'alg'") {
224 t.Errorf("the error for id token with header that did not contain alg did not trigger")
225 }
226
227 _, err = jv.VerifyIdToken("ew0KICAiYWxnIjogIlJTMjU2IiwNCiAgImFuZCI6ICJ0aGlzIg0KfQ.aa.aa")
228
229 if !strings.Contains(err.Error(), "header must contain a 'kid'") {
230 t.Errorf("the error for id token with header that did not contain kid did not trigger")
231 }
232 }
233
234 func Test_an_id_token_header_that_is_not_rs256_throws_an_error(t *testing.T) {
235 jvs := JwtVerifier{
236 Issuer: "https://golang.oktapreview.com",
237 }
238
239 jv := jvs.New()
240
241 _, err := jv.VerifyIdToken("ew0KICAia2lkIjogImFiYzEyMyIsDQogICJhbGciOiAiSFMyNTYiDQp9.aa.aa")
242
243 if !strings.Contains(err.Error(), "only supported alg is RS256") {
244 t.Errorf("the error for id token with with wrong alg did not trigger")
245 }
246 }
247
248
249 func Test_invalid_formatting_of_access_token_throws_an_error(t *testing.T) {
250 jvs := JwtVerifier{
251 Issuer: "https://golang.oktapreview.com",
252 }
253
254 jv := jvs.New()
255
256 _, err := jv.VerifyAccessToken("aa")
257
258 if err == nil {
259 t.Errorf("an error was not thrown when an access token does not contain at least 1 period ('.')")
260 }
261
262 if !strings.Contains(err.Error(), "token must contain at least 1 period ('.')") {
263 t.Errorf("the error for access token with no periods did not trigger")
264 }
265 }
266
267 func Test_an_access_token_header_that_is_improperly_formatted_throws_an_error(t *testing.T) {
268 jvs := JwtVerifier{
269 Issuer: "https://golang.oktapreview.com",
270 }
271
272 jv := jvs.New()
273
274 _, err := jv.VerifyAccessToken("123456789.aa.aa")
275
276 if !strings.Contains(err.Error(), "does not appear to be a base64 encoded string") {
277 t.Errorf("the error for access token with header that is not base64 encoded did not trigger")
278 }
279 }
280
281 func Test_an_access_token_header_that_is_not_decoded_into_json_throws_an_error(t *testing.T) {
282 jvs := JwtVerifier{
283 Issuer: "https://golang.oktapreview.com",
284 }
285
286 jv := jvs.New()
287
288 _, err := jv.VerifyAccessToken("aa.aa.aa")
289
290 if !strings.Contains(err.Error(), "not a json object") {
291 t.Errorf("the error for access token with header that is not a json object did not trigger")
292 }
293 }
294
295 func Test_an_access_token_header_that_is_not_contain_the_correct_parts_throws_an_error(t *testing.T) {
296 jvs := JwtVerifier{
297 Issuer: "https://golang.oktapreview.com",
298 }
299
300 jv := jvs.New()
301
302 _, err := jv.VerifyAccessToken("ew0KICAia2lkIjogImFiYzEyMyIsDQogICJhbmQiOiAidGhpcyINCn0.aa.aa")
303
304 if !strings.Contains(err.Error(), "header must contain an 'alg'") {
305 t.Errorf("the error for access token with header that did not contain alg did not trigger")
306 }
307
308 _, err = jv.VerifyAccessToken("ew0KICAiYWxnIjogIlJTMjU2IiwNCiAgImFuZCI6ICJ0aGlzIg0KfQ.aa.aa")
309
310 if !strings.Contains(err.Error(), "header must contain a 'kid'") {
311 t.Errorf("the error for access token with header that did not contain kid did not trigger")
312 }
313 }
314
315 func Test_an_access_token_header_that_is_not_rs256_throws_an_error(t *testing.T) {
316 jvs := JwtVerifier{
317 Issuer: "https://golang.oktapreview.com",
318 }
319
320 jv := jvs.New()
321
322 _, err := jv.VerifyAccessToken("ew0KICAia2lkIjogImFiYzEyMyIsDQogICJhbGciOiAiSFMyNTYiDQp9.aa.aa")
323
324 if !strings.Contains(err.Error(), "only supported alg is RS256") {
325 t.Errorf("the error for access token with with wrong alg did not trigger")
326 }
327 }
328
329 func Test_a_successful_authentication_can_have_its_tokens_parsed(t *testing.T) {
330 utils.ParseEnvironment()
331
332 if os.Getenv("ISSUER") == "" || os.Getenv("CLIENT_ID") == "" {
333 log.Printf("Skipping integration tests")
334 t.Skip("appears that environment variables are not set, skipping the integration test for now")
335 }
336
337 type AuthnResponse struct {
338 SessionToken string `json:"sessionToken"`
339 }
340
341 nonce, err := utils.GenerateNonce()
342 if err != nil {
343 t.Errorf("could not generate nonce")
344 }
345
346
347 issuerParts, _ := url.Parse(os.Getenv("ISSUER"))
348 baseUrl := issuerParts.Scheme + "://" + issuerParts.Hostname()
349 requestUri := baseUrl + "/api/v1/authn"
350 postValues := map[string]string{"username": os.Getenv("USERNAME"), "password": os.Getenv("PASSWORD")}
351 postJsonValues, _ := json.Marshal(postValues)
352 resp, err := http.Post(requestUri, "application/json", bytes.NewReader(postJsonValues))
353 if err != nil {
354 t.Errorf("could not submit authentication endpoint")
355 }
356 defer resp.Body.Close()
357 body, _ := ioutil.ReadAll(resp.Body)
358
359 var authn AuthnResponse
360 err = json.Unmarshal(body, &authn)
361 if err != nil {
362 t.Errorf("could not unmarshal authn response")
363 }
364
365
366 authzUri := os.Getenv("ISSUER") + "/v1/authorize?client_id=" + os.Getenv(
367 "CLIENT_ID") + "&nonce=" + nonce + "&redirect_uri=http://localhost:8080/implicit/callback" +
368 "&response_type=token%20id_token&scope=openid&state" +
369 "=ApplicationState&sessionToken=" + authn.SessionToken
370
371 client := &http.Client{
372 CheckRedirect: func(req *http.Request, with []*http.Request) error {
373 return http.ErrUseLastResponse
374 },
375 }
376
377 resp, err = client.Get(authzUri)
378
379 if err != nil {
380 t.Errorf("could not submit authorization endpoint: %s", err.Error())
381 }
382
383 defer resp.Body.Close()
384 location := resp.Header.Get("Location")
385 locParts, _ := url.Parse(location)
386 fragmentParts, _ := url.ParseQuery(locParts.Fragment)
387
388 if fragmentParts["access_token"] == nil {
389 t.Errorf("could not extract access_token")
390 }
391
392 if fragmentParts["id_token"] == nil {
393 t.Errorf("could not extract id_token")
394 }
395
396 accessToken := fragmentParts["access_token"][0]
397 idToken := fragmentParts["id_token"][0]
398
399 tv := map[string]string{}
400 tv["aud"] = os.Getenv("CLIENT_ID")
401 tv["nonce"] = nonce
402 jv := JwtVerifier{
403 Issuer: os.Getenv("ISSUER"),
404 ClaimsToValidate: tv,
405 }
406
407 claims, err := jv.New().VerifyIdToken(idToken)
408 if err != nil {
409 t.Errorf("could not verify id_token: %s", err.Error())
410 }
411
412 issuer := claims.Claims["iss"]
413
414 if issuer == nil {
415 t.Errorf("issuer claim could not be pulled from access_token")
416 }
417
418 tv = map[string]string{}
419 tv["aud"] = "api://default"
420 tv["cid"] = os.Getenv("CLIENT_ID")
421 jv = JwtVerifier{
422 Issuer: os.Getenv("ISSUER"),
423 ClaimsToValidate: tv,
424 }
425
426 claims, err = jv.New().VerifyAccessToken(accessToken)
427
428 if err != nil {
429 t.Errorf("could not verify access_token: %s", err.Error())
430 }
431
432 issuer = claims.Claims["iss"]
433
434 if issuer == nil {
435 t.Errorf("issuer claim could not be pulled from access_token")
436 }
437
438
439 tv = map[string]string{}
440 tv["aud"] = "api://default"
441 jv = JwtVerifier{
442 Issuer: os.Getenv("ISSUER"),
443 ClaimsToValidate: tv,
444 }
445
446 claims, err = jv.New().VerifyAccessToken(accessToken)
447
448 if err != nil {
449 t.Errorf("could not verify access_token: %s", err.Error())
450 }
451
452 issuer = claims.Claims["iss"]
453
454 if issuer == nil {
455 t.Errorf("issuer claim could not be pulled from access_token")
456 }
457 }
458
View as plain text