omniauth-auth0 2.4.0 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33f4a34bf39a6fb628e07ed669624f1917d07353ed6f1b90d1a7e49f159c34f0
4
- data.tar.gz: 75b2362d94d4dfaa802a5a858c2b6c9c01dbd594393670c69280f21b96732ff2
3
+ metadata.gz: baf9ee46a227506a7f43d571bbf9b6afd3639f8bf83cb32cc8ef8a55af5041ab
4
+ data.tar.gz: a7529eca35711ab1217e9946c4c5872a4a8d5296773bc49425f63a2792bf40f0
5
5
  SHA512:
6
- metadata.gz: bd3d007acf54fdf777fd9793eb24a0512504969436bc88420f51c21647fb4099815bb099e980e1d27bcbb3e187efb3a767bdb9b59b9f08b549ebf9e011072bc9
7
- data.tar.gz: 36c5a4202d76c35d52dfdf1168895a03a66cad08400150444642f1dae4c9a285b0c856ffcbed3d49763c14a9c2201dc8ab96e3dffa9c50aad3c8c201e2784f59
6
+ metadata.gz: b315bd912671314bcb0fd2f3bb19878ee1029fe3738c7887b732c3c346794011e23c26a39d0d77f9644846c302480469d00d1b307d6d72e6be61d9a6aa8b9e37
7
+ data.tar.gz: e29b464b82e4d4c3ef8870d06e1eee5f279afaf3330afa8a1167dbf4bfe0795ae4c966006c8bc6ebd993ac579ffe1cbd9cecb4aceb827a153af9e651020ea8cd
@@ -1,5 +1,13 @@
1
1
  # Change Log
2
2
 
3
+ ## [v2.4.1](https://github.com/auth0/omniauth-auth0/tree/v2.4.1) (2020-10-08)
4
+
5
+ [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.4.0...v2.4.1)
6
+
7
+ **Fixed**
8
+ - Verify the JWT Signature [\#109](https://github.com/auth0/omniauth-auth0/pull/109) ([jimmyjames](https://github.com/jimmyjames))
9
+
10
+
3
11
  ## [v2.4.0](https://github.com/auth0/omniauth-auth0/tree/v2.4.0) (2020-09-22)
4
12
 
5
13
  [Full Changelog](https://github.com/auth0/omniauth-auth0/compare/v2.3.1...v2.4.0)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- omniauth-auth0 (2.4.0)
4
+ omniauth-auth0 (2.4.1)
5
5
  omniauth-oauth2 (~> 1.5)
6
6
 
7
7
  GEM
@@ -71,7 +71,7 @@ GEM
71
71
  oauth2 (~> 1.4)
72
72
  omniauth (~> 1.9)
73
73
  parallel (1.19.2)
74
- parser (2.7.1.4)
74
+ parser (2.7.2.0)
75
75
  ast (~> 2.4.1)
76
76
  pry (0.13.1)
77
77
  coderay (~> 1.1)
@@ -87,13 +87,13 @@ GEM
87
87
  rb-fsevent (0.10.4)
88
88
  rb-inotify (0.10.1)
89
89
  ffi (~> 1.0)
90
- regexp_parser (1.8.0)
90
+ regexp_parser (1.8.1)
91
91
  rexml (3.2.4)
92
92
  rspec (3.9.0)
93
93
  rspec-core (~> 3.9.0)
94
94
  rspec-expectations (~> 3.9.0)
95
95
  rspec-mocks (~> 3.9.0)
96
- rspec-core (3.9.2)
96
+ rspec-core (3.9.3)
97
97
  rspec-support (~> 3.9.3)
98
98
  rspec-expectations (3.9.2)
99
99
  diff-lcs (>= 1.2.0, < 2.0)
@@ -102,17 +102,17 @@ GEM
102
102
  diff-lcs (>= 1.2.0, < 2.0)
103
103
  rspec-support (~> 3.9.0)
104
104
  rspec-support (3.9.3)
105
- rubocop (0.91.0)
105
+ rubocop (0.93.0)
106
106
  parallel (~> 1.10)
107
- parser (>= 2.7.1.1)
107
+ parser (>= 2.7.1.5)
108
108
  rainbow (>= 2.2.2, < 4.0)
109
- regexp_parser (>= 1.7)
109
+ regexp_parser (>= 1.8)
110
110
  rexml
111
- rubocop-ast (>= 0.4.0, < 1.0)
111
+ rubocop-ast (>= 0.6.0)
112
112
  ruby-progressbar (~> 1.7)
113
113
  unicode-display_width (>= 1.4.0, < 2.0)
114
- rubocop-ast (0.4.2)
115
- parser (>= 2.7.1.4)
114
+ rubocop-ast (0.7.1)
115
+ parser (>= 2.7.1.5)
116
116
  ruby-progressbar (1.10.1)
117
117
  ruby2_keywords (0.0.2)
118
118
  ruby_dep (1.5.0)
@@ -122,7 +122,7 @@ GEM
122
122
  simplecov (0.19.0)
123
123
  docile (~> 1.1)
124
124
  simplecov-html (~> 0.11)
125
- simplecov-html (0.12.2)
125
+ simplecov-html (0.12.3)
126
126
  sinatra (2.1.0)
127
127
  mustermann (~> 1.0)
128
128
  rack (~> 2.2)
@@ -1,5 +1,5 @@
1
1
  module OmniAuth
2
2
  module Auth0
3
- VERSION = '2.4.0'.freeze
3
+ VERSION = '2.4.1'.freeze
4
4
  end
5
5
  end
@@ -28,17 +28,24 @@ module OmniAuth
28
28
  @client_secret = options.client_secret
29
29
  end
30
30
 
31
+ # Verify a token's signature. Only tokens signed with the RS256 or HS256 signatures are supported.
32
+ # @return array - The token's key and signing algorithm
31
33
  def verify_signature(jwt)
32
34
  head = token_head(jwt)
33
35
 
34
36
  # Make sure the algorithm is supported and get the decode key.
35
37
  if head[:alg] == 'RS256'
36
- [rs256_decode_key(head[:kid]), head[:alg]]
38
+ key, alg = [rs256_decode_key(head[:kid]), head[:alg]]
37
39
  elsif head[:alg] == 'HS256'
38
- [@client_secret, head[:alg]]
40
+ key, alg = [@client_secret, head[:alg]]
39
41
  else
40
42
  raise OmniAuth::Auth0::TokenValidationError.new("Signature algorithm of #{head[:alg]} is not supported. Expected the ID token to be signed with RS256 or HS256")
41
43
  end
44
+
45
+ # Call decode to verify the signature
46
+ JWT.decode(jwt, key, true, decode_opts(alg))
47
+
48
+ return key, alg
42
49
  end
43
50
 
44
51
  # Verify a JWT.
@@ -93,11 +100,27 @@ module OmniAuth
93
100
  end
94
101
 
95
102
  private
103
+ # Get the JWT decode options. We disable the claim checks since we perform our claim validation logic
104
+ # Docs: https://github.com/jwt/ruby-jwt
105
+ # @return hash
106
+ def decode_opts(alg)
107
+ {
108
+ algorithm: alg,
109
+ verify_expiration: false,
110
+ verify_iat: false,
111
+ verify_iss: false,
112
+ verify_aud: false,
113
+ verify_jti: false,
114
+ verify_subj: false,
115
+ verify_not_before: false
116
+ }
117
+ end
118
+
96
119
  def rs256_decode_key(kid)
97
120
  jwks_x5c = jwks_key(:x5c, kid)
98
121
 
99
122
  if jwks_x5c.nil?
100
- raise OmniAuth::Auth0::TokenValidationError.new("Could not find a public key for Key ID (kid) '#{kid}''")
123
+ raise OmniAuth::Auth0::TokenValidationError.new("Could not find a public key for Key ID (kid) '#{kid}'")
101
124
  end
102
125
 
103
126
  jwks_public_cert(jwks_x5c.first)
@@ -12,17 +12,17 @@ describe OmniAuth::Auth0::JWTValidator do
12
12
  let(:domain) { 'samples.auth0.com' }
13
13
  let(:future_timecode) { 32_503_680_000 }
14
14
  let(:past_timecode) { 303_912_000 }
15
- let(:jwks_kid) { 'NkJCQzIyQzRBMEU4NjhGNUU4MzU4RkY0M0ZDQzkwOUQ0Q0VGNUMwQg' }
15
+ let(:valid_jwks_kid) { 'NkJCQzIyQzRBMEU4NjhGNUU4MzU4RkY0M0ZDQzkwOUQ0Q0VGNUMwQg' }
16
16
 
17
17
  let(:rsa_private_key) do
18
18
  OpenSSL::PKey::RSA.generate 2048
19
19
  end
20
20
 
21
- let(:rsa_token_jwks) do
21
+ let(:valid_jwks) do
22
22
  {
23
23
  keys: [
24
24
  {
25
- kid: jwks_kid,
25
+ kid: valid_jwks_kid,
26
26
  x5c: [Base64.encode64(make_cert(rsa_private_key).to_der)]
27
27
  }
28
28
  ]
@@ -91,29 +91,29 @@ describe OmniAuth::Auth0::JWTValidator do
91
91
  end
92
92
  end
93
93
 
94
- describe 'JWT verifier jwks_key' do
94
+ describe 'JWT verifier jwks key parsing' do
95
95
  let(:jwt_validator) do
96
96
  make_jwt_validator
97
97
  end
98
98
 
99
99
  before do
100
- stub_jwks
100
+ stub_complete_jwks
101
101
  end
102
102
 
103
103
  it 'should return a key' do
104
- expect(jwt_validator.jwks_key(:alg, jwks_kid)).to eq('RS256')
104
+ expect(jwt_validator.jwks_key(:alg, valid_jwks_kid)).to eq('RS256')
105
105
  end
106
106
 
107
107
  it 'should return an x5c key' do
108
- expect(jwt_validator.jwks_key(:x5c, jwks_kid).length).to eq(1)
108
+ expect(jwt_validator.jwks_key(:x5c, valid_jwks_kid).length).to eq(1)
109
109
  end
110
110
 
111
111
  it 'should return nil if there is not key' do
112
- expect(jwt_validator.jwks_key(:auth0, jwks_kid)).to eq(nil)
112
+ expect(jwt_validator.jwks_key(:auth0, valid_jwks_kid)).to eq(nil)
113
113
  end
114
114
 
115
115
  it 'should return nil if the key ID is invalid' do
116
- expect(jwt_validator.jwks_key(:alg, "#{jwks_kid}_invalid")).to eq(nil)
116
+ expect(jwt_validator.jwks_key(:alg, "#{valid_jwks_kid}_invalid")).to eq(nil)
117
117
  end
118
118
  end
119
119
 
@@ -153,8 +153,24 @@ describe OmniAuth::Auth0::JWTValidator do
153
153
  end
154
154
 
155
155
  before do
156
- stub_jwks
157
- stub_dummy_jwks
156
+ stub_complete_jwks
157
+ stub_expected_jwks
158
+ end
159
+
160
+ it 'should fail when JWT is nil' do
161
+ expect do
162
+ jwt_validator.verify(nil)
163
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
164
+ message: "ID token is required but missing"
165
+ }))
166
+ end
167
+
168
+ it 'should fail when JWT is not well-formed' do
169
+ expect do
170
+ jwt_validator.verify('abc.123')
171
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
172
+ message: "ID token could not be decoded"
173
+ }))
158
174
  end
159
175
 
160
176
  it 'should fail with missing issuer' do
@@ -248,6 +264,39 @@ describe OmniAuth::Auth0::JWTValidator do
248
264
  }))
249
265
  end
250
266
 
267
+ it 'should pass when past expiration but within default leeway' do
268
+ exp = Time.now.to_i - 59
269
+ payload = {
270
+ iss: "https://#{domain}/",
271
+ sub: 'sub',
272
+ aud: client_id,
273
+ exp: exp,
274
+ iat: past_timecode
275
+ }
276
+
277
+ token = make_hs256_token(payload)
278
+ id_token = jwt_validator.verify(token)
279
+ expect(id_token['exp']).to eq(exp)
280
+ end
281
+
282
+ it 'should fail when past expiration and outside default leeway' do
283
+ exp = Time.now.to_i - 61
284
+ payload = {
285
+ iss: "https://#{domain}/",
286
+ sub: 'sub',
287
+ aud: client_id,
288
+ exp: exp,
289
+ iat: past_timecode
290
+ }
291
+
292
+ token = make_hs256_token(payload)
293
+ expect do
294
+ jwt_validator.verify(token)
295
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
296
+ message: "Expiration time (exp) claim error in the ID token; current time (#{Time.now}) is after expiration time (#{Time.at(exp + 60)})"
297
+ }))
298
+ end
299
+
251
300
  it 'should fail when missing iat' do
252
301
  payload = {
253
302
  iss: "https://#{domain}/",
@@ -377,6 +426,114 @@ describe OmniAuth::Auth0::JWTValidator do
377
426
  }))
378
427
  end
379
428
 
429
+ it 'should fail when “max_age” sent on the authentication request and this claim added the “max_age” value doesn’t represent a date in the future, outside the default leeway' do
430
+ now = Time.now.to_i
431
+ auth_time = now - 121
432
+ max_age = 60
433
+ payload = {
434
+ iss: "https://#{domain}/",
435
+ sub: 'sub',
436
+ aud: client_id,
437
+ exp: future_timecode,
438
+ iat: past_timecode,
439
+ auth_time: auth_time
440
+ }
441
+
442
+ token = make_hs256_token(payload)
443
+ expect do
444
+ jwt_validator.verify(token, { max_age: max_age })
445
+ # Time.at(auth_time + max_age + leeway
446
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
447
+ message: "Authentication Time (auth_time) claim in the ID token indicates that too much time has passed since the last end-user authentication. Current time (#{Time.now}) is after last auth time (#{Time.at(auth_time + max_age + 60)})"
448
+ }))
449
+ end
450
+
451
+ it 'should verify when “max_age” sent on the authentication request and this claim added the “max_age” value doesn’t represent a date in the future, outside the default leeway' do
452
+ now = Time.now.to_i
453
+ auth_time = now - 119
454
+ max_age = 60
455
+ payload = {
456
+ iss: "https://#{domain}/",
457
+ sub: 'sub',
458
+ aud: client_id,
459
+ exp: future_timecode,
460
+ iat: past_timecode,
461
+ auth_time: auth_time
462
+ }
463
+
464
+ token = make_hs256_token(payload)
465
+ id_token = jwt_validator.verify(token, { max_age: max_age })
466
+ expect(id_token['auth_time']).to eq(auth_time)
467
+ end
468
+
469
+ it 'should fail for RS256 token when kid is incorrect' do
470
+ domain = 'example.org'
471
+ sub = 'abc123'
472
+ payload = {
473
+ sub: sub,
474
+ exp: future_timecode,
475
+ iss: "https://#{domain}/",
476
+ iat: past_timecode,
477
+ aud: client_id
478
+ }
479
+ invalid_kid = 'invalid-kid'
480
+ token = make_rs256_token(payload, invalid_kid)
481
+ expect do
482
+ verified_token = make_jwt_validator(opt_domain: domain).verify(token)
483
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
484
+ message: "Could not find a public key for Key ID (kid) 'invalid-kid'"
485
+ }))
486
+ end
487
+
488
+ it 'should fail when RS256 token has invalid signature' do
489
+ domain = 'example.org'
490
+ sub = 'abc123'
491
+ payload = {
492
+ sub: sub,
493
+ exp: future_timecode,
494
+ iss: "https://#{domain}/",
495
+ iat: past_timecode,
496
+ aud: client_id
497
+ }
498
+ token = make_rs256_token(payload) + 'bad'
499
+ expect do
500
+ verified_token = make_jwt_validator(opt_domain: domain).verify(token)
501
+ end.to raise_error(an_instance_of(JWT::VerificationError).and having_attributes({
502
+ message: "Signature verification raised"
503
+ }))
504
+ end
505
+
506
+ it 'should fail when algorithm is not RS256 or HS256' do
507
+ payload = {
508
+ iss: "https://#{domain}/",
509
+ sub: 'abc123',
510
+ aud: client_id,
511
+ exp: future_timecode,
512
+ iat: past_timecode
513
+ }
514
+ token = JWT.encode payload, 'secret', 'HS384'
515
+ expect do
516
+ jwt_validator.verify(token)
517
+ end.to raise_error(an_instance_of(OmniAuth::Auth0::TokenValidationError).and having_attributes({
518
+ message: "Signature algorithm of HS384 is not supported. Expected the ID token to be signed with RS256 or HS256"
519
+ }))
520
+ end
521
+
522
+ it 'should fail when HS256 token has invalid signature' do
523
+ payload = {
524
+ iss: "https://#{domain}/",
525
+ sub: 'abc123',
526
+ aud: client_id,
527
+ exp: future_timecode,
528
+ iat: past_timecode
529
+ }
530
+ token = make_hs256_token(payload, 'bad_secret')
531
+ expect do
532
+ # validator is configured to use "CLIENT_SECRET" by default
533
+ jwt_validator.verify(token)
534
+ end.to raise_error(an_instance_of(JWT::VerificationError))
535
+ end
536
+
380
537
  it 'should verify a valid HS256 token with multiple audiences' do
381
538
  audience = [
382
539
  client_id,
@@ -417,13 +574,44 @@ describe OmniAuth::Auth0::JWTValidator do
417
574
  exp: future_timecode,
418
575
  iss: "https://#{domain}/",
419
576
  iat: past_timecode,
420
- aud: client_id,
421
- kid: jwks_kid
577
+ aud: client_id
422
578
  }
423
579
  token = make_rs256_token(payload)
424
580
  verified_token = make_jwt_validator(opt_domain: domain).verify(token)
425
581
  expect(verified_token['sub']).to eq(sub)
426
582
  end
583
+
584
+ it 'should verify a HS256 JWT signature when calling verify signature directly' do
585
+ sub = 'abc123'
586
+ payload = {
587
+ iss: "https://#{domain}/",
588
+ sub: sub,
589
+ aud: client_id,
590
+ exp: future_timecode,
591
+ iat: past_timecode
592
+ }
593
+ token = make_hs256_token(payload)
594
+ verified_token_signature = jwt_validator.verify_signature(token)
595
+ expect(verified_token_signature[0]).to eq('CLIENT_SECRET')
596
+ expect(verified_token_signature[1]).to eq('HS256')
597
+ end
598
+
599
+ it 'should verify a RS256 JWT signature verify signature directly' do
600
+ domain = 'example.org'
601
+ sub = 'abc123'
602
+ payload = {
603
+ sub: sub,
604
+ exp: future_timecode,
605
+ iss: "https://#{domain}/",
606
+ iat: past_timecode,
607
+ aud: client_id
608
+ }
609
+ token = make_rs256_token(payload)
610
+ verified_token_signature = make_jwt_validator(opt_domain: domain).verify_signature(token)
611
+ expect(verified_token_signature.length).to be(2)
612
+ expect(verified_token_signature[0]).to be_a(OpenSSL::PKey::RSA)
613
+ expect(verified_token_signature[1]).to eq('RS256')
614
+ end
427
615
  end
428
616
 
429
617
  private
@@ -439,14 +627,16 @@ describe OmniAuth::Auth0::JWTValidator do
439
627
  OmniAuth::Auth0::JWTValidator.new(opts)
440
628
  end
441
629
 
442
- def make_hs256_token(payload = nil)
630
+ def make_hs256_token(payload = nil, secret = nil)
443
631
  payload = { sub: 'abc123' } if payload.nil?
444
- JWT.encode payload, client_secret, 'HS256'
632
+ secret = client_secret if secret.nil?
633
+ JWT.encode payload, secret, 'HS256'
445
634
  end
446
635
 
447
- def make_rs256_token(payload = nil)
636
+ def make_rs256_token(payload = nil, kid = nil)
448
637
  payload = { sub: 'abc123' } if payload.nil?
449
- JWT.encode payload, rsa_private_key, 'RS256', kid: jwks_kid
638
+ kid = valid_jwks_kid if kid.nil?
639
+ JWT.encode payload, rsa_private_key, 'RS256', kid: kid
450
640
  end
451
641
 
452
642
  def make_cert(private_key)
@@ -474,7 +664,7 @@ describe OmniAuth::Auth0::JWTValidator do
474
664
  cert.sign private_key, OpenSSL::Digest::SHA1.new
475
665
  end
476
666
 
477
- def stub_jwks
667
+ def stub_complete_jwks
478
668
  stub_request(:get, 'https://samples.auth0.com/.well-known/jwks.json')
479
669
  .to_return(
480
670
  headers: { 'Content-Type' => 'application/json' },
@@ -483,18 +673,11 @@ describe OmniAuth::Auth0::JWTValidator do
483
673
  )
484
674
  end
485
675
 
486
- def stub_bad_jwks
487
- stub_request(:get, 'https://samples.auth0.com/.well-known/jwks-bad.json')
488
- .to_return(
489
- status: 404
490
- )
491
- end
492
-
493
- def stub_dummy_jwks
676
+ def stub_expected_jwks
494
677
  stub_request(:get, 'https://example.org/.well-known/jwks.json')
495
678
  .to_return(
496
679
  headers: { 'Content-Type' => 'application/json' },
497
- body: rsa_token_jwks,
680
+ body: valid_jwks,
498
681
  status: 200
499
682
  )
500
683
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniauth-auth0
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Auth0
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-23 00:00:00.000000000 Z
11
+ date: 2020-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: omniauth-oauth2
@@ -88,7 +88,7 @@ homepage: https://github.com/auth0/omniauth-auth0
88
88
  licenses:
89
89
  - MIT
90
90
  metadata: {}
91
- post_install_message:
91
+ post_install_message:
92
92
  rdoc_options: []
93
93
  require_paths:
94
94
  - lib
@@ -104,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
104
  version: '0'
105
105
  requirements: []
106
106
  rubygems_version: 3.1.2
107
- signing_key:
107
+ signing_key:
108
108
  specification_version: 4
109
109
  summary: OmniAuth OAuth2 strategy for the Auth0 platform.
110
110
  test_files: