omniauth-auth0 2.4.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: