jwt 1.5.0 → 2.5.0

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.
Files changed (56) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +8 -0
  3. data/.github/workflows/coverage.yml +27 -0
  4. data/.github/workflows/test.yml +67 -0
  5. data/.gitignore +13 -0
  6. data/.reek.yml +22 -0
  7. data/.rspec +2 -0
  8. data/.rubocop.yml +67 -0
  9. data/.sourcelevel.yml +17 -0
  10. data/AUTHORS +119 -0
  11. data/Appraisals +13 -0
  12. data/CHANGELOG.md +786 -0
  13. data/CODE_OF_CONDUCT.md +84 -0
  14. data/CONTRIBUTING.md +99 -0
  15. data/Gemfile +7 -0
  16. data/LICENSE +7 -0
  17. data/README.md +639 -0
  18. data/Rakefile +13 -14
  19. data/lib/jwt/algos/ecdsa.rb +64 -0
  20. data/lib/jwt/algos/eddsa.rb +35 -0
  21. data/lib/jwt/algos/hmac.rb +36 -0
  22. data/lib/jwt/algos/none.rb +17 -0
  23. data/lib/jwt/algos/ps.rb +43 -0
  24. data/lib/jwt/algos/rsa.rb +22 -0
  25. data/lib/jwt/algos/unsupported.rb +19 -0
  26. data/lib/jwt/algos.rb +44 -0
  27. data/lib/jwt/base64.rb +19 -0
  28. data/lib/jwt/claims_validator.rb +37 -0
  29. data/lib/jwt/configuration/container.rb +21 -0
  30. data/lib/jwt/configuration/decode_configuration.rb +46 -0
  31. data/lib/jwt/configuration/jwk_configuration.rb +27 -0
  32. data/lib/jwt/configuration.rb +15 -0
  33. data/lib/jwt/decode.rb +145 -0
  34. data/lib/jwt/encode.rb +69 -0
  35. data/lib/jwt/error.rb +22 -0
  36. data/lib/jwt/json.rb +10 -22
  37. data/lib/jwt/jwk/ec.rb +199 -0
  38. data/lib/jwt/jwk/hmac.rb +67 -0
  39. data/lib/jwt/jwk/key_base.rb +35 -0
  40. data/lib/jwt/jwk/key_finder.rb +62 -0
  41. data/lib/jwt/jwk/kid_as_key_digest.rb +15 -0
  42. data/lib/jwt/jwk/rsa.rb +138 -0
  43. data/lib/jwt/jwk/thumbprint.rb +26 -0
  44. data/lib/jwt/jwk.rb +52 -0
  45. data/lib/jwt/security_utils.rb +59 -0
  46. data/lib/jwt/signature.rb +35 -0
  47. data/lib/jwt/verify.rb +113 -0
  48. data/lib/jwt/version.rb +28 -0
  49. data/lib/jwt/x5c_key_finder.rb +55 -0
  50. data/lib/jwt.rb +20 -215
  51. data/ruby-jwt.gemspec +35 -0
  52. metadata +138 -30
  53. data/Manifest +0 -6
  54. data/jwt.gemspec +0 -34
  55. data/spec/helper.rb +0 -2
  56. data/spec/jwt_spec.rb +0 -434
metadata CHANGED
@@ -1,71 +1,179 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jwt
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
- - Jeff Lindsay
8
- autorequire:
7
+ - Tim Rudat
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-09 00:00:00.000000000 Z
11
+ date: 2022-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: echoe
14
+ name: appraisal
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 4.6.3
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 4.6.3
27
- description: JSON Web Token implementation in Ruby
28
- email: progrium@gmail.com
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: reek
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: A pure ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT)
98
+ standard.
99
+ email: timrudat@gmail.com
29
100
  executables: []
30
101
  extensions: []
31
- extra_rdoc_files:
32
- - lib/jwt.rb
33
- - lib/jwt/json.rb
102
+ extra_rdoc_files: []
34
103
  files:
35
- - Manifest
104
+ - ".codeclimate.yml"
105
+ - ".github/workflows/coverage.yml"
106
+ - ".github/workflows/test.yml"
107
+ - ".gitignore"
108
+ - ".reek.yml"
109
+ - ".rspec"
110
+ - ".rubocop.yml"
111
+ - ".sourcelevel.yml"
112
+ - AUTHORS
113
+ - Appraisals
114
+ - CHANGELOG.md
115
+ - CODE_OF_CONDUCT.md
116
+ - CONTRIBUTING.md
117
+ - Gemfile
118
+ - LICENSE
119
+ - README.md
36
120
  - Rakefile
37
- - jwt.gemspec
38
121
  - lib/jwt.rb
122
+ - lib/jwt/algos.rb
123
+ - lib/jwt/algos/ecdsa.rb
124
+ - lib/jwt/algos/eddsa.rb
125
+ - lib/jwt/algos/hmac.rb
126
+ - lib/jwt/algos/none.rb
127
+ - lib/jwt/algos/ps.rb
128
+ - lib/jwt/algos/rsa.rb
129
+ - lib/jwt/algos/unsupported.rb
130
+ - lib/jwt/base64.rb
131
+ - lib/jwt/claims_validator.rb
132
+ - lib/jwt/configuration.rb
133
+ - lib/jwt/configuration/container.rb
134
+ - lib/jwt/configuration/decode_configuration.rb
135
+ - lib/jwt/configuration/jwk_configuration.rb
136
+ - lib/jwt/decode.rb
137
+ - lib/jwt/encode.rb
138
+ - lib/jwt/error.rb
39
139
  - lib/jwt/json.rb
40
- - spec/helper.rb
41
- - spec/jwt_spec.rb
42
- homepage: http://github.com/progrium/ruby-jwt
140
+ - lib/jwt/jwk.rb
141
+ - lib/jwt/jwk/ec.rb
142
+ - lib/jwt/jwk/hmac.rb
143
+ - lib/jwt/jwk/key_base.rb
144
+ - lib/jwt/jwk/key_finder.rb
145
+ - lib/jwt/jwk/kid_as_key_digest.rb
146
+ - lib/jwt/jwk/rsa.rb
147
+ - lib/jwt/jwk/thumbprint.rb
148
+ - lib/jwt/security_utils.rb
149
+ - lib/jwt/signature.rb
150
+ - lib/jwt/verify.rb
151
+ - lib/jwt/version.rb
152
+ - lib/jwt/x5c_key_finder.rb
153
+ - ruby-jwt.gemspec
154
+ homepage: https://github.com/jwt/ruby-jwt
43
155
  licenses:
44
156
  - MIT
45
- metadata: {}
46
- post_install_message:
47
- rdoc_options:
48
- - "--line-numbers"
49
- - "--title"
50
- - Jwt
51
- - "--main"
52
- - README.md
157
+ metadata:
158
+ bug_tracker_uri: https://github.com/jwt/ruby-jwt/issues
159
+ changelog_uri: https://github.com/jwt/ruby-jwt/blob/v2.5.0/CHANGELOG.md
160
+ post_install_message:
161
+ rdoc_options: []
53
162
  require_paths:
54
163
  - lib
55
164
  required_ruby_version: !ruby/object:Gem::Requirement
56
165
  requirements:
57
166
  - - ">="
58
167
  - !ruby/object:Gem::Version
59
- version: '0'
168
+ version: '2.5'
60
169
  required_rubygems_version: !ruby/object:Gem::Requirement
61
170
  requirements:
62
171
  - - ">="
63
172
  - !ruby/object:Gem::Version
64
- version: '1.2'
173
+ version: '0'
65
174
  requirements: []
66
- rubyforge_project: jwt
67
- rubygems_version: 2.4.6
68
- signing_key:
175
+ rubygems_version: 3.3.21
176
+ signing_key:
69
177
  specification_version: 4
70
178
  summary: JSON Web Token implementation in Ruby
71
179
  test_files: []
data/Manifest DELETED
@@ -1,6 +0,0 @@
1
- Rakefile
2
- lib/jwt.rb
3
- lib/jwt/json.rb
4
- spec/helper.rb
5
- spec/jwt_spec.rb
6
- Manifest
data/jwt.gemspec DELETED
@@ -1,34 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
- # stub: jwt 1.5.0 ruby lib
3
-
4
- Gem::Specification.new do |s|
5
- s.name = "jwt"
6
- s.version = "1.5.0"
7
-
8
- s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
9
- s.require_paths = ["lib"]
10
- s.authors = ["Jeff Lindsay"]
11
- s.date = "2015-05-09"
12
- s.description = "JSON Web Token implementation in Ruby"
13
- s.email = "progrium@gmail.com"
14
- s.extra_rdoc_files = ["lib/jwt.rb", "lib/jwt/json.rb"]
15
- s.files = ["Manifest", "Rakefile", "jwt.gemspec", "lib/jwt.rb", "lib/jwt/json.rb", "spec/helper.rb", "spec/jwt_spec.rb"]
16
- s.homepage = "http://github.com/progrium/ruby-jwt"
17
- s.licenses = ["MIT"]
18
- s.rdoc_options = ["--line-numbers", "--title", "Jwt", "--main", "README.md"]
19
- s.rubyforge_project = "jwt"
20
- s.rubygems_version = "2.4.6"
21
- s.summary = "JSON Web Token implementation in Ruby"
22
-
23
- if s.respond_to? :specification_version then
24
- s.specification_version = 4
25
-
26
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
27
- s.add_development_dependency(%q<echoe>, [">= 4.6.3"])
28
- else
29
- s.add_dependency(%q<echoe>, [">= 4.6.3"])
30
- end
31
- else
32
- s.add_dependency(%q<echoe>, [">= 4.6.3"])
33
- end
34
- end
data/spec/helper.rb DELETED
@@ -1,2 +0,0 @@
1
- require 'rspec'
2
- require "#{File.dirname(__FILE__)}/../lib/jwt.rb"
data/spec/jwt_spec.rb DELETED
@@ -1,434 +0,0 @@
1
- require 'helper'
2
-
3
- describe JWT do
4
- before do
5
- @payload = {'foo' => 'bar', 'exp' => Time.now.to_i + 1, 'nbf' => Time.now.to_i - 1 }
6
- end
7
-
8
- it 'encodes and decodes JWTs' do
9
- secret = 'secret'
10
- jwt = JWT.encode(@payload, secret)
11
- decoded_payload = JWT.decode(jwt, secret)
12
- expect(decoded_payload).to include(@payload)
13
- end
14
-
15
- it 'encodes and decodes JWTs for RSA signatures' do
16
- private_key = OpenSSL::PKey::RSA.generate(512)
17
- jwt = JWT.encode(@payload, private_key, 'RS256')
18
- decoded_payload = JWT.decode(jwt, private_key.public_key)
19
- expect(decoded_payload).to include(@payload)
20
- end
21
-
22
- it 'encodes and decodes JWTs for ECDSA P-256 signatures' do
23
- private_key = OpenSSL::PKey::EC.new('prime256v1')
24
- private_key.generate_key
25
- public_key = OpenSSL::PKey::EC.new(private_key)
26
- public_key.private_key = nil
27
- jwt = JWT.encode(@payload, private_key, 'ES256')
28
- decoded_payload = JWT.decode(jwt, public_key)
29
- expect(decoded_payload).to include(@payload)
30
- end
31
-
32
- it 'encodes and decodes JWTs for ECDSA P-384 signatures' do
33
- private_key = OpenSSL::PKey::EC.new('secp384r1')
34
- private_key.generate_key
35
- public_key = OpenSSL::PKey::EC.new(private_key)
36
- public_key.private_key = nil
37
- jwt = JWT.encode(@payload, private_key, 'ES384')
38
- decoded_payload = JWT.decode(jwt, public_key)
39
- expect(decoded_payload).to include(@payload)
40
- end
41
-
42
- it 'encodes and decodes JWTs for ECDSA P-521 signatures' do
43
- private_key = OpenSSL::PKey::EC.new('secp521r1')
44
- private_key.generate_key
45
- public_key = OpenSSL::PKey::EC.new(private_key)
46
- public_key.private_key = nil
47
- jwt = JWT.encode(@payload, private_key, 'ES512')
48
- decoded_payload = JWT.decode(jwt, public_key)
49
- expect(decoded_payload).to include(@payload)
50
- end
51
-
52
- it 'encodes and decodes JWTs with custom header fields' do
53
- private_key = OpenSSL::PKey::RSA.generate(512)
54
- jwt = JWT.encode(@payload, private_key, 'RS256', {'kid' => 'default'})
55
- decoded_payload = JWT.decode(jwt) do |header|
56
- expect(header['kid']).to eq('default')
57
- private_key.public_key
58
- end
59
- expect(decoded_payload).to include(@payload)
60
- end
61
-
62
- it 'raises encode exception when ECDSA algorithm does not match key' do
63
- private_key = OpenSSL::PKey::EC.new('prime256v1')
64
- private_key.generate_key
65
- expect do
66
- JWT.encode(@payload, private_key, 'ES512')
67
- end.to raise_error(JWT::IncorrectAlgorithm, 'payload algorithm is ES512 but ES256 signing key was provided')
68
- end
69
-
70
- it 'decodes valid JWTs' do
71
- example_payload = {'hello' => 'world'}
72
- example_secret = 'secret'
73
- example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
74
- decoded_payload = JWT.decode(example_jwt, example_secret)
75
- expect(decoded_payload).to include(example_payload)
76
- end
77
-
78
- it 'decodes valid JWTs with iss' do
79
- example_payload = {'hello' => 'world', 'iss' => 'jwtiss'}
80
- example_secret = 'secret'
81
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaXNzIjoiand0aXNzIn0.nTZkyYfpGUyKULaj45lXw_1gXXjHvGW4h5V7okHdUqQ'
82
- decoded_payload = JWT.decode(example_jwt, example_secret, true, {'iss' => 'jwtiss'})
83
- expect(decoded_payload).to include(example_payload)
84
- end
85
-
86
- it 'raises invalid issuer' do
87
- # example_payload = {'hello' => 'world', 'iss' => 'jwtiss'}
88
- example_payload2 = {'hello' => 'world'}
89
-
90
- example_secret = 'secret'
91
-
92
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaXNzIjoiand0aXNzIn0.nTZkyYfpGUyKULaj45lXw_1gXXjHvGW4h5V7okHdUqQ'
93
- expect{ JWT.decode(example_jwt, example_secret, true, {:verify_iss => true, 'iss' => 'jwt_iss'}) }.to raise_error(JWT::InvalidIssuerError)
94
-
95
- example_jwt2 = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
96
- decode_payload2 = JWT.decode(example_jwt2, example_secret, true, {'iss' => 'jwt_iss'})
97
- expect(decode_payload2).to include(example_payload2)
98
- end
99
-
100
- it 'decodes valid JWTs with iat' do
101
- example_payload = {'hello' => 'world', 'iat' => 1425917209}
102
- example_secret = 'secret'
103
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5fQ.m4F-Ugo7aLnLunBBO3BeDidyWMx8T9eoJz6FW2rgQhU'
104
- decoded_payload = JWT.decode(example_jwt, example_secret, true, {'iat' => true})
105
- expect(decoded_payload).to include(example_payload)
106
- end
107
-
108
- it 'raises decode exception when iat is invalid' do
109
- # example_payload = {'hello' => 'world', 'iat' => 'abc'}
110
- example_secret = 'secret'
111
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoiMTQyNTkxNzIwOSJ9.Mn_vk61xWjIhbXFqAB0nFmNkDiCmfzUgl_LaCKRT6S8'
112
- expect{ JWT.decode(example_jwt, example_secret, true, {:verify_iat => true, 'iat' => 1425917209}) }.to raise_error(JWT::InvalidIatError)
113
- end
114
-
115
- it 'decodes valid JWTs with jti' do
116
- example_payload = {'hello' => 'world', 'iat' => 1425917209, 'jti' => Digest::MD5.hexdigest('secret:1425917209')}
117
- example_secret = 'secret'
118
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5LCJqdGkiOiI1NWM3NzZlMjFmN2NiZDg3OWMwNmZhYzAxOGRhYzQwMiJ9.ET0hb-VTUOL3M22oG13ofzvGPLMAncbF8rdNDIqo8tg'
119
- decoded_payload = JWT.decode(example_jwt, example_secret, true, {'jti' => Digest::MD5.hexdigest('secret:1425917209')})
120
- expect(decoded_payload).to include(example_payload)
121
- end
122
-
123
- it 'raises decode exception when jti is invalid' do
124
- # example_payload = {'hello' => 'world', 'iat' => 1425917209, 'jti' => Digest::MD5.hexdigest('secret:1425917209')}
125
- example_secret = 'secret'
126
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5LCJqdGkiOiI1NWM3NzZlMjFmN2NiZDg3OWMwNmZhYzAxOGRhYzQwMiJ9.ET0hb-VTUOL3M22oG13ofzvGPLMAncbF8rdNDIqo8tg'
127
- expect{ JWT.decode(example_jwt, example_secret, true, {:verify_jti => true, 'jti' => Digest::MD5.hexdigest('secret:1425922032')}) }.to raise_error(JWT::InvalidJtiError)
128
- # expect{ JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::InvalidJtiError)
129
- end
130
-
131
- it 'raises decode exception when jti without iat' do
132
- # example_payload = {'hello' => 'world', 'jti' => Digest::MD5.hexdigest('secret:1425917209')}
133
- example_secret = 'secret'
134
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwianRpIjoiNTVjNzc2ZTIxZjdjYmQ4NzljMDZmYWMwMThkYWM0MDIifQ.n0foJCnCM_-_xUvG_TOmR9mYpL2y0UqZOD_gv33djeE'
135
- expect{ JWT.decode(example_jwt, example_secret, true, {:verify_jti => true, 'jti' => Digest::MD5.hexdigest('secret:1425922032')}) }.to raise_error(JWT::InvalidJtiError)
136
- end
137
-
138
- it 'decodes valid JWTs with aud' do
139
- example_payload = {'hello' => 'world', 'aud' => 'url:pnd'}
140
- example_payload2 = {'hello' => 'world', 'aud' => ['url:pnd', 'aud:yes']}
141
- example_secret = 'secret'
142
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiYXVkIjoidXJsOnBuZCJ9._gT5veUtNiZD7wLEC6Gd0-nkQV3cl1z8G0zXq8qcd-8'
143
- example_jwt2 = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiYXVkIjpbInVybDpwbmQiLCJhdWQ6eWVzIl19.qNPNcT4X9B5uI91rIwbW2bIPTsp8wbRYW3jkZkrmqbQ'
144
- decoded_payload = JWT.decode(example_jwt, example_secret, true, {'aud' => 'url:pnd'})
145
- decoded_payload2 = JWT.decode(example_jwt2, example_secret, true, {'aud' => 'url:pnd'})
146
- expect(decoded_payload).to include(example_payload)
147
- expect(decoded_payload2).to include(example_payload2)
148
- end
149
-
150
- it 'raises deode exception when aud is invalid' do
151
- # example_payload = {'hello' => 'world', 'aud' => 'url:pnd'}
152
- example_secret = 'secret'
153
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiYXVkIjoidXJsOnBuZCJ9._gT5veUtNiZD7wLEC6Gd0-nkQV3cl1z8G0zXq8qcd-8'
154
- expect{ JWT.decode(example_jwt, example_secret, true, {:verify_aud => true, 'aud' => 'wrong:aud'}) }.to raise_error(JWT::InvalidAudError)
155
- end
156
-
157
- it 'decodes valid JWTs with sub' do
158
- example_payload = {'hello' => 'world', 'sub' => 'subject'}
159
- example_secret = 'secret'
160
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwic3ViIjoic3ViamVjdCJ9.QUnNVZm4SPB4vP2zY9m1LoUSOx-5oGXBhj7R89D_UtA'
161
- decoded_payload = JWT.decode(example_jwt, example_secret, true, {'sub' => 'subject'})
162
- expect(decoded_payload).to include(example_payload)
163
- end
164
-
165
- it 'raise decode exception when the sub is invalid' do
166
- # example_payload = {'hello' => 'world', 'sub' => 'subject'}
167
- # TODO: Test not working
168
- example_secret = 'secret'
169
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwic3ViIjoic3ViamVjdCJ9.QUnNVZm4SPB4vP2zY9m1LoUSOx-5oGXBhj7R89D_UtA'
170
- # expect{ JWT.decode(example_jwt, example_secret, true, {:verify_iss => true, 'iss' => 'subject'}) }.to raise_error(JWT::InvalidSubError)
171
- end
172
-
173
- it 'raises decode exception when the token is invalid' do
174
- example_secret = 'secret'
175
- # Same as above exmaple with some random bytes replaced
176
- example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHiMomlwIjogIkJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
177
- expect { JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::DecodeError)
178
- end
179
-
180
- it 'raises verification exception with wrong hmac key' do
181
- right_secret = 'foo'
182
- bad_secret = 'bar'
183
- jwt_message = JWT.encode(@payload, right_secret, 'HS256')
184
- expect { JWT.decode(jwt_message, bad_secret) }.to raise_error(JWT::VerificationError)
185
- end
186
-
187
- it 'raises decode exception when ECDSA algorithm does not match key' do
188
- right_private_key = OpenSSL::PKey::EC.new('prime256v1')
189
- right_private_key.generate_key
190
- right_public_key = OpenSSL::PKey::EC.new(right_private_key)
191
- right_public_key.private_key = nil
192
- bad_private_key = OpenSSL::PKey::EC.new('secp384r1')
193
- bad_private_key.generate_key
194
- bad_public_key = OpenSSL::PKey::EC.new(bad_private_key)
195
- bad_public_key.private_key = nil
196
- jwt = JWT.encode(@payload, right_private_key, 'ES256')
197
- expect do
198
- JWT.decode(jwt, bad_public_key)
199
- end.to raise_error(JWT::IncorrectAlgorithm, 'payload algorithm is ES256 but ES384 verification key was provided')
200
- end
201
-
202
- it 'raises verification exception with wrong rsa key' do
203
- right_private_key = OpenSSL::PKey::RSA.generate(512)
204
- bad_private_key = OpenSSL::PKey::RSA.generate(512)
205
- jwt = JWT.encode(@payload, right_private_key, 'RS256')
206
- expect { JWT.decode(jwt, bad_private_key.public_key) }.to raise_error(JWT::VerificationError)
207
- end
208
-
209
- it 'raises verification exception with wrong ECDSA key' do
210
- right_private_key = OpenSSL::PKey::EC.new('prime256v1')
211
- right_private_key.generate_key
212
- bad_private_key = OpenSSL::PKey::EC.new('prime256v1')
213
- bad_private_key.generate_key
214
- bad_public_key = OpenSSL::PKey::EC.new(bad_private_key)
215
- bad_public_key.private_key = nil
216
- jwt = JWT.encode(@payload, right_private_key, 'ES256')
217
- expect { JWT.decode(jwt, bad_public_key) }.to raise_error(JWT::VerificationError)
218
- end
219
-
220
- it 'raises decode exception with invalid signature' do
221
- example_secret = 'secret'
222
- example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.'
223
- expect { JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::DecodeError)
224
- end
225
-
226
- it 'raises decode exception with nonexistent header' do
227
- expect { JWT.decode('..stuff') }.to raise_error(JWT::DecodeError)
228
- end
229
-
230
- it 'raises decode exception with nonexistent payload' do
231
- expect { JWT.decode('eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9..stuff') }.to raise_error(JWT::DecodeError)
232
- end
233
-
234
- it 'raises decode exception with nil jwt' do
235
- expect { JWT.decode(nil) }.to raise_error(JWT::DecodeError)
236
- end
237
-
238
- it 'allows decoding without key' do
239
- right_secret = 'foo'
240
- bad_secret = 'bar'
241
- jwt = JWT.encode(@payload, right_secret)
242
- decoded_payload = JWT.decode(jwt, bad_secret, false)
243
- expect(decoded_payload).to include(@payload)
244
- end
245
-
246
- it 'checks the key when verify is truthy' do
247
- right_secret = 'foo'
248
- bad_secret = 'bar'
249
- jwt = JWT.encode(@payload, right_secret)
250
- verify = 'yes' =~ /^y/i
251
- expect { JWT.decode(jwt, bad_secret, verify) }.to raise_error(JWT::DecodeError)
252
- end
253
-
254
- it 'raises exception on unsupported crypto algorithm' do
255
- expect { JWT.encode(@payload, 'secret', 'HS1024') }.to raise_error(NotImplementedError)
256
- end
257
-
258
- it 'raises exception when decoded with a different algorithm than it was encoded with' do
259
- jwt = JWT.encode(@payload, 'foo', 'HS384')
260
- expect { JWT.decode(jwt, 'foo', true, :algorithm => 'HS512') }.to raise_error(JWT::IncorrectAlgorithm)
261
- end
262
-
263
- it 'does not raise exception when encoded with the expected algorithm' do
264
- jwt = JWT.encode(@payload, 'foo', 'HS512')
265
- JWT.decode(jwt, 'foo', true, :algorithm => 'HS512')
266
- end
267
-
268
- it 'encodes and decodes plaintext JWTs' do
269
- jwt = JWT.encode(@payload, nil, nil)
270
- expect(jwt.split('.').length).to eq(2)
271
- decoded_payload = JWT.decode(jwt, nil, nil)
272
- expect(decoded_payload).to include(@payload)
273
- end
274
-
275
- it 'requires a signature segment when verify is truthy' do
276
- jwt = JWT.encode(@payload, nil, nil)
277
- expect(jwt.split('.').length).to eq(2)
278
- expect { JWT.decode(jwt, nil, true) }.to raise_error(JWT::DecodeError)
279
- end
280
-
281
- it 'does not use == to compare digests' do
282
- secret = 'secret'
283
- jwt = JWT.encode(@payload, secret)
284
- crypto_segment = jwt.split('.').last
285
-
286
- signature = JWT.base64url_decode(crypto_segment)
287
- expect(signature).not_to receive('==')
288
- expect(JWT).to receive(:base64url_decode).with(crypto_segment).once.and_return(signature)
289
- expect(JWT).to receive(:base64url_decode).at_least(:once).and_call_original
290
-
291
- JWT.decode(jwt, secret)
292
- end
293
-
294
- it 'raises error when expired' do
295
- expired_payload = @payload.clone
296
- expired_payload['exp'] = Time.now.to_i - 1
297
- secret = 'secret'
298
- jwt = JWT.encode(expired_payload, secret)
299
- expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ExpiredSignature)
300
- end
301
-
302
- it 'raise ExpiredSignature even when exp claims is a string' do
303
- expired_payload = @payload.clone
304
- expired_payload['exp'] = (Time.now.to_i).to_s
305
- secret = 'secret'
306
- jwt = JWT.encode(expired_payload, secret)
307
- expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ExpiredSignature)
308
- end
309
-
310
- it 'performs normal decode with skipped expiration check' do
311
- expired_payload = @payload.clone
312
- expired_payload['exp'] = Time.now.to_i - 1
313
- secret = 'secret'
314
- jwt = JWT.encode(expired_payload, secret)
315
- decoded_payload = JWT.decode(jwt, secret, true, {:verify_expiration => false})
316
- expect(decoded_payload).to include(expired_payload)
317
- end
318
-
319
- it 'performs normal decode using leeway' do
320
- expired_payload = @payload.clone
321
- expired_payload['exp'] = Time.now.to_i - 2
322
- secret = 'secret'
323
- jwt = JWT.encode(expired_payload, secret)
324
- decoded_payload = JWT.decode(jwt, secret, true, {:leeway => 3})
325
- expect(decoded_payload).to include(expired_payload)
326
- end
327
-
328
- it 'raises error when before nbf' do
329
- immature_payload = @payload.clone
330
- immature_payload['nbf'] = Time.now.to_i + 1
331
- secret = 'secret'
332
- jwt = JWT.encode(immature_payload, secret)
333
- expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ImmatureSignature)
334
- end
335
-
336
- it 'doesnt raise error when after nbf' do
337
- mature_payload = @payload.clone
338
- secret = 'secret'
339
- jwt = JWT.encode(mature_payload, secret)
340
- decoded_payload = JWT.decode(jwt, secret, true, {:verify_expiration => false})
341
- expect(decoded_payload).to include(mature_payload)
342
- end
343
-
344
- it 'raise ImmatureSignature even when nbf claim is a string' do
345
- immature_payload = @payload.clone
346
- immature_payload['nbf'] = (Time.now.to_i).to_s
347
- secret = 'secret'
348
- jwt = JWT.encode(immature_payload, secret)
349
- expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ImmatureSignature)
350
- end
351
-
352
- it 'performs normal decode with skipped not before check' do
353
- immature_payload = @payload.clone
354
- immature_payload['nbf'] = Time.now.to_i + 2
355
- secret = 'secret'
356
- jwt = JWT.encode(immature_payload, secret)
357
- decoded_payload = JWT.decode(jwt, secret, true, {:verify_not_before => false})
358
- expect(decoded_payload).to include(immature_payload)
359
- end
360
-
361
- it 'performs normal decode using leeway' do
362
- immature_payload = @payload.clone
363
- immature_payload['nbf'] = Time.now.to_i - 2
364
- secret = 'secret'
365
- jwt = JWT.encode(immature_payload, secret)
366
- decoded_payload = JWT.decode(jwt, secret, true, {:leeway => 3})
367
- expect(decoded_payload).to include(immature_payload)
368
- end
369
-
370
- describe 'secure comparison' do
371
- it 'returns true if strings are equal' do
372
- expect(JWT.secure_compare('Foo', 'Foo')).to be true
373
- end
374
-
375
- it 'returns false if either input is nil or empty' do
376
- [nil, ''].each do |bad|
377
- expect(JWT.secure_compare(bad, 'Foo')).to be false
378
- expect(JWT.secure_compare('Foo', bad)).to be false
379
- end
380
- end
381
-
382
- it 'retuns false if the strings are different' do
383
- expect(JWT.secure_compare('Foo', 'Bar')).to be false
384
- end
385
- end
386
-
387
- # no method should leave OpenSSL.errors populated
388
- after do
389
- expect(OpenSSL.errors).to be_empty
390
- end
391
-
392
- it 'raise exception on invalid signature' do
393
- pubkey = OpenSSL::PKey::RSA.new(<<-PUBKEY)
394
- -----BEGIN PUBLIC KEY-----
395
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCaY7425h964bjaoLeUm
396
- SlZ8sK7VtVk9zHbGmZh2ygGYwfuUf2bmMye2Ofv99yDE/rd4loVIAcu7RVvDRgHq
397
- 3/CZTnIrSvHsiJQsHBNa3d+F1ihPfzURzf1M5k7CFReBj2SBXhDXd57oRfBQj12w
398
- CVhhwP6kGTAWuoppbIIIBfNF2lE/Nvm7lVVYQqL9xOrP/AQ4xRbpQlB8Ll9sO9Or
399
- SvbWhCDa/LMOWxHdmrcJi6XoSg1vnOyCoKbyAoauTt/XqdkHbkDdQ6HFbJieu9il
400
- LDZZNliPhfENuKeC2MCGVXTEu8Cqhy1w6e4axavLlXoYf4laJIZ/e7au8SqDbY0B
401
- xwIDAQAB
402
- -----END PUBLIC KEY-----
403
- PUBKEY
404
- jwt = (
405
- 'eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiY' +
406
- 'XVkIjoiMTA2MDM1Nzg5MTY4OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSI' +
407
- 'sImNpZCI6IjEwNjAzNTc4OTE2ODguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb' +
408
- '20iLCJpZCI6IjExNjQ1MjgyNDMwOTg1Njc4MjE2MyIsInRva2VuX2hhc2giOiJ' +
409
- '0Z2hEOUo4bjhWME4ydmN3NmVNaWpnIiwiaWF0IjoxMzIwNjcwOTc4LCJleHAiO' +
410
- 'jEzMjA2NzQ4Nzh9.D8x_wirkxDElqKdJBcsIws3Ogesk38okz6MN7zqC7nEAA7' +
411
- 'wcy1PxsROY1fmBvXSer0IQesAqOW-rPOCNReSn-eY8d53ph1x2HAF-AzEi3GOl' +
412
- '6hFycH8wj7Su6JqqyEbIVLxE7q7DkAZGaMPkxbTHs1EhSd5_oaKQ6O4xO3ZnnT4'
413
- )
414
- expect { JWT.decode(jwt, pubkey, true) }.to raise_error(JWT::DecodeError)
415
- end
416
-
417
- describe 'urlsafe base64 encoding' do
418
- it 'replaces + and / with - and _' do
419
- allow(Base64).to receive(:encode64) { 'string+with/non+url-safe/characters_' }
420
- expect(JWT.base64url_encode('foo')).to eq('string-with_non-url-safe_characters_')
421
- end
422
- end
423
-
424
- describe 'decoded_segments' do
425
- it 'allows access to the decoded header and payload' do
426
- secret = 'secret'
427
- jwt = JWT.encode(@payload, secret)
428
- decoded_segments = JWT.decoded_segments(jwt)
429
- expect(decoded_segments.size).to eq(4)
430
- expect(decoded_segments[0]).to eq({'typ' => 'JWT', 'alg' => 'HS256'})
431
- expect(decoded_segments[1]).to eq(@payload)
432
- end
433
- end
434
- end