jwt 2.1.0 → 2.7.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 (80) hide show
  1. checksums.yaml +5 -5
  2. data/AUTHORS +119 -0
  3. data/CHANGELOG.md +355 -19
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/CONTRIBUTING.md +99 -0
  6. data/README.md +331 -102
  7. data/lib/jwt/algos/algo_wrapper.rb +30 -0
  8. data/lib/jwt/algos/ecdsa.rb +39 -12
  9. data/lib/jwt/algos/eddsa.rb +18 -8
  10. data/lib/jwt/algos/hmac.rb +57 -17
  11. data/lib/jwt/algos/hmac_rbnacl.rb +53 -0
  12. data/lib/jwt/algos/hmac_rbnacl_fixed.rb +52 -0
  13. data/lib/jwt/algos/none.rb +19 -0
  14. data/lib/jwt/algos/ps.rb +41 -0
  15. data/lib/jwt/algos/rsa.rb +7 -5
  16. data/lib/jwt/algos/unsupported.rb +7 -4
  17. data/lib/jwt/algos.rb +67 -0
  18. data/lib/jwt/base64.rb +19 -0
  19. data/lib/jwt/claims_validator.rb +37 -0
  20. data/lib/jwt/configuration/container.rb +21 -0
  21. data/lib/jwt/configuration/decode_configuration.rb +46 -0
  22. data/lib/jwt/configuration/jwk_configuration.rb +27 -0
  23. data/lib/jwt/configuration.rb +15 -0
  24. data/lib/jwt/decode.rb +143 -24
  25. data/lib/jwt/encode.rb +54 -26
  26. data/lib/jwt/error.rb +6 -0
  27. data/lib/jwt/json.rb +18 -0
  28. data/lib/jwt/jwk/ec.rb +236 -0
  29. data/lib/jwt/jwk/hmac.rb +103 -0
  30. data/lib/jwt/jwk/key_base.rb +55 -0
  31. data/lib/jwt/jwk/key_finder.rb +46 -0
  32. data/lib/jwt/jwk/kid_as_key_digest.rb +15 -0
  33. data/lib/jwt/jwk/okp_rbnacl.rb +110 -0
  34. data/lib/jwt/jwk/rsa.rb +203 -0
  35. data/lib/jwt/jwk/set.rb +80 -0
  36. data/lib/jwt/jwk/thumbprint.rb +26 -0
  37. data/lib/jwt/jwk.rb +55 -0
  38. data/lib/jwt/security_utils.rb +8 -27
  39. data/lib/jwt/verify.rb +19 -8
  40. data/lib/jwt/version.rb +22 -2
  41. data/lib/jwt/x5c_key_finder.rb +55 -0
  42. data/lib/jwt.rb +12 -44
  43. data/ruby-jwt.gemspec +18 -10
  44. metadata +45 -118
  45. data/.codeclimate.yml +0 -20
  46. data/.ebert.yml +0 -18
  47. data/.gitignore +0 -11
  48. data/.reek.yml +0 -40
  49. data/.rspec +0 -1
  50. data/.rubocop.yml +0 -98
  51. data/.travis.yml +0 -14
  52. data/Gemfile +0 -3
  53. data/Manifest +0 -8
  54. data/Rakefile +0 -11
  55. data/lib/jwt/default_options.rb +0 -15
  56. data/lib/jwt/signature.rb +0 -50
  57. data/spec/fixtures/certs/ec256-private.pem +0 -8
  58. data/spec/fixtures/certs/ec256-public.pem +0 -4
  59. data/spec/fixtures/certs/ec256-wrong-private.pem +0 -8
  60. data/spec/fixtures/certs/ec256-wrong-public.pem +0 -4
  61. data/spec/fixtures/certs/ec384-private.pem +0 -9
  62. data/spec/fixtures/certs/ec384-public.pem +0 -5
  63. data/spec/fixtures/certs/ec384-wrong-private.pem +0 -9
  64. data/spec/fixtures/certs/ec384-wrong-public.pem +0 -5
  65. data/spec/fixtures/certs/ec512-private.pem +0 -10
  66. data/spec/fixtures/certs/ec512-public.pem +0 -6
  67. data/spec/fixtures/certs/ec512-wrong-private.pem +0 -10
  68. data/spec/fixtures/certs/ec512-wrong-public.pem +0 -6
  69. data/spec/fixtures/certs/rsa-1024-private.pem +0 -15
  70. data/spec/fixtures/certs/rsa-1024-public.pem +0 -6
  71. data/spec/fixtures/certs/rsa-2048-private.pem +0 -27
  72. data/spec/fixtures/certs/rsa-2048-public.pem +0 -9
  73. data/spec/fixtures/certs/rsa-2048-wrong-private.pem +0 -27
  74. data/spec/fixtures/certs/rsa-2048-wrong-public.pem +0 -9
  75. data/spec/fixtures/certs/rsa-4096-private.pem +0 -51
  76. data/spec/fixtures/certs/rsa-4096-public.pem +0 -14
  77. data/spec/integration/readme_examples_spec.rb +0 -202
  78. data/spec/jwt/verify_spec.rb +0 -232
  79. data/spec/jwt_spec.rb +0 -315
  80. data/spec/spec_helper.rb +0 -28
@@ -1,202 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../spec_helper'
4
- require 'jwt'
5
-
6
- describe 'README.md code test' do
7
- context 'algorithm usage' do
8
- let(:payload) { { data: 'test' } }
9
-
10
- it 'NONE' do
11
- token = JWT.encode payload, nil, 'none'
12
- decoded_token = JWT.decode token, nil, false
13
-
14
- expect(token).to eq 'eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9.'
15
- expect(decoded_token).to eq [
16
- { 'data' => 'test' },
17
- { 'alg' => 'none' }
18
- ]
19
- end
20
-
21
- it 'HMAC' do
22
- token = JWT.encode payload, 'my$ecretK3y', 'HS256'
23
- decoded_token = JWT.decode token, 'my$ecretK3y', false
24
-
25
- expect(token).to eq 'eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y'
26
- expect(decoded_token).to eq [
27
- { 'data' => 'test' },
28
- { 'alg' => 'HS256' }
29
- ]
30
- end
31
-
32
- it 'RSA' do
33
- rsa_private = OpenSSL::PKey::RSA.generate 2048
34
- rsa_public = rsa_private.public_key
35
-
36
- token = JWT.encode payload, rsa_private, 'RS256'
37
- decoded_token = JWT.decode token, rsa_public, true, algorithm: 'RS256'
38
-
39
- expect(decoded_token).to eq [
40
- { 'data' => 'test' },
41
- { 'alg' => 'RS256' }
42
- ]
43
- end
44
-
45
- it 'ECDSA' do
46
- ecdsa_key = OpenSSL::PKey::EC.new 'prime256v1'
47
- ecdsa_key.generate_key
48
- ecdsa_public = OpenSSL::PKey::EC.new ecdsa_key
49
- ecdsa_public.private_key = nil
50
-
51
- token = JWT.encode payload, ecdsa_key, 'ES256'
52
- decoded_token = JWT.decode token, ecdsa_public, true, algorithm: 'ES256'
53
-
54
- expect(decoded_token).to eq [
55
- { 'data' => 'test' },
56
- { 'alg' => 'ES256' }
57
- ]
58
- end
59
- end
60
-
61
- context 'claims' do
62
- let(:hmac_secret) { 'MyP4ssW0rD' }
63
-
64
- context 'exp' do
65
- it 'without leeway' do
66
- exp = Time.now.to_i + 4 * 3600
67
- exp_payload = { data: 'data', exp: exp }
68
-
69
- token = JWT.encode exp_payload, hmac_secret, 'HS256'
70
-
71
- expect do
72
- JWT.decode token, hmac_secret, true, algorithm: 'HS256'
73
- end.not_to raise_error
74
- end
75
-
76
- it 'with leeway' do
77
- exp = Time.now.to_i - 10
78
- leeway = 30 # seconds
79
-
80
- exp_payload = { data: 'data', exp: exp }
81
-
82
- token = JWT.encode exp_payload, hmac_secret, 'HS256'
83
-
84
- expect do
85
- JWT.decode token, hmac_secret, true, leeway: leeway, algorithm: 'HS256'
86
- end.not_to raise_error
87
- end
88
- end
89
-
90
- context 'nbf' do
91
- it 'without leeway' do
92
- nbf = Time.now.to_i - 3600
93
- nbf_payload = { data: 'data', nbf: nbf }
94
- token = JWT.encode nbf_payload, hmac_secret, 'HS256'
95
-
96
- expect do
97
- JWT.decode token, hmac_secret, true, algorithm: 'HS256'
98
- end.not_to raise_error
99
- end
100
-
101
- it 'with leeway' do
102
- nbf = Time.now.to_i + 10
103
- leeway = 30
104
- nbf_payload = { data: 'data', nbf: nbf }
105
- token = JWT.encode nbf_payload, hmac_secret, 'HS256'
106
-
107
- expect do
108
- JWT.decode token, hmac_secret, true, leeway: leeway, algorithm: 'HS256'
109
- end.not_to raise_error
110
- end
111
- end
112
-
113
- it 'iss' do
114
- iss = 'My Awesome Company Inc. or https://my.awesome.website/'
115
- iss_payload = { data: 'data', iss: iss }
116
-
117
- token = JWT.encode iss_payload, hmac_secret, 'HS256'
118
-
119
- expect do
120
- JWT.decode token, hmac_secret, true, iss: iss, algorithm: 'HS256'
121
- end.not_to raise_error
122
- end
123
-
124
- context 'aud' do
125
- it 'array' do
126
- aud = %w[Young Old]
127
- aud_payload = { data: 'data', aud: aud }
128
-
129
- token = JWT.encode aud_payload, hmac_secret, 'HS256'
130
-
131
- expect do
132
- JWT.decode token, hmac_secret, true, aud: %w[Old Young], verify_aud: true, algorithm: 'HS256'
133
- end.not_to raise_error
134
- end
135
-
136
- it 'string' do
137
- expect do
138
- end.not_to raise_error
139
- end
140
- end
141
-
142
- it 'jti' do
143
- iat = Time.now.to_i
144
- hmac_secret = 'test'
145
- jti_raw = [hmac_secret, iat].join(':').to_s
146
- jti = Digest::MD5.hexdigest(jti_raw)
147
- jti_payload = { data: 'data', iat: iat, jti: jti }
148
-
149
- token = JWT.encode jti_payload, hmac_secret, 'HS256'
150
-
151
- expect do
152
- JWT.decode token, hmac_secret, true, verify_jti: true, algorithm: 'HS256'
153
- end.not_to raise_error
154
- end
155
-
156
- context 'iat' do
157
- it 'without leeway' do
158
- iat = Time.now.to_i
159
- iat_payload = { data: 'data', iat: iat }
160
-
161
- token = JWT.encode iat_payload, hmac_secret, 'HS256'
162
-
163
- expect do
164
- JWT.decode token, hmac_secret, true, verify_iat: true, algorithm: 'HS256'
165
- end.not_to raise_error
166
- end
167
-
168
- it 'with leeway' do
169
- iat = Time.now.to_i - 7
170
- iat_payload = { data: 'data', iat: iat, leeway: 10 }
171
-
172
- token = JWT.encode iat_payload, hmac_secret, 'HS256'
173
-
174
- expect do
175
- JWT.decode token, hmac_secret, true, verify_iat: true, algorithm: 'HS256'
176
- end.not_to raise_error
177
- end
178
- end
179
-
180
- context 'custom header fields' do
181
- it 'with custom field' do
182
- payload = { data: 'test' }
183
-
184
- token = JWT.encode payload, nil, 'none', typ: 'JWT'
185
- _, header = JWT.decode token, nil, false
186
-
187
- expect(header['typ']).to eq 'JWT'
188
- end
189
- end
190
-
191
- it 'sub' do
192
- sub = 'Subject'
193
- sub_payload = { data: 'data', sub: sub }
194
-
195
- token = JWT.encode sub_payload, hmac_secret, 'HS256'
196
-
197
- expect do
198
- JWT.decode token, hmac_secret, true, 'sub' => sub, :verify_sub => true, :algorithm => 'HS256'
199
- end.not_to raise_error
200
- end
201
- end
202
- end
@@ -1,232 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
- require 'jwt/verify'
5
-
6
- module JWT
7
- RSpec.describe Verify do
8
- let(:base_payload) { { 'user_id' => 'some@user.tld' } }
9
- let(:options) { { leeway: 0 } }
10
-
11
- context '.verify_aud(payload, options)' do
12
- let(:scalar_aud) { 'ruby-jwt-aud' }
13
- let(:array_aud) { %w[ruby-jwt-aud test-aud ruby-ruby-ruby] }
14
- let(:scalar_payload) { base_payload.merge('aud' => scalar_aud) }
15
- let(:array_payload) { base_payload.merge('aud' => array_aud) }
16
-
17
- it 'must raise JWT::InvalidAudError when the singular audience does not match' do
18
- expect do
19
- Verify.verify_aud(scalar_payload, options.merge(aud: 'no-match'))
20
- end.to raise_error JWT::InvalidAudError
21
- end
22
-
23
- it 'must raise JWT::InvalidAudError when the payload has an array and none match the supplied value' do
24
- expect do
25
- Verify.verify_aud(array_payload, options.merge(aud: 'no-match'))
26
- end.to raise_error JWT::InvalidAudError
27
- end
28
-
29
- it 'must allow a matching singular audience to pass' do
30
- Verify.verify_aud(scalar_payload, options.merge(aud: scalar_aud))
31
- end
32
-
33
- it 'must allow an array with any value matching the one in the options' do
34
- Verify.verify_aud(array_payload, options.merge(aud: array_aud.first))
35
- end
36
-
37
- it 'must allow an array with any value matching any value in the options array' do
38
- Verify.verify_aud(array_payload, options.merge(aud: array_aud))
39
- end
40
-
41
- it 'must allow a singular audience payload matching any value in the options array' do
42
- Verify.verify_aud(scalar_payload, options.merge(aud: array_aud))
43
- end
44
- end
45
-
46
- context '.verify_expiration(payload, options)' do
47
- let(:payload) { base_payload.merge('exp' => (Time.now.to_i - 5)) }
48
-
49
- it 'must raise JWT::ExpiredSignature when the token has expired' do
50
- expect do
51
- Verify.verify_expiration(payload, options)
52
- end.to raise_error JWT::ExpiredSignature
53
- end
54
-
55
- it 'must allow some leeway in the expiration when global leeway is configured' do
56
- Verify.verify_expiration(payload, options.merge(leeway: 10))
57
- end
58
-
59
- it 'must allow some leeway in the expiration when exp_leeway is configured' do
60
- Verify.verify_expiration(payload, options.merge(exp_leeway: 10))
61
- end
62
-
63
- it 'must be expired if the exp claim equals the current time' do
64
- payload['exp'] = Time.now.to_i
65
-
66
- expect do
67
- Verify.verify_expiration(payload, options)
68
- end.to raise_error JWT::ExpiredSignature
69
- end
70
-
71
- context 'when leeway is not specified' do
72
- let(:options) { {} }
73
-
74
- it 'used a default leeway of 0' do
75
- expect do
76
- Verify.verify_expiration(payload, options)
77
- end.to raise_error JWT::ExpiredSignature
78
- end
79
- end
80
- end
81
-
82
- context '.verify_iat(payload, options)' do
83
- let(:iat) { Time.now.to_f }
84
- let(:payload) { base_payload.merge('iat' => iat) }
85
-
86
- it 'must allow a valid iat' do
87
- Verify.verify_iat(payload, options)
88
- end
89
-
90
- it 'must allow configured leeway' do
91
- Verify.verify_iat(payload.merge('iat' => (iat + 60)), options.merge(leeway: 70))
92
- end
93
-
94
- it 'must allow configured iat_leeway' do
95
- Verify.verify_iat(payload.merge('iat' => (iat + 60)), options.merge(iat_leeway: 70))
96
- end
97
-
98
- it 'must properly handle integer times' do
99
- Verify.verify_iat(payload.merge('iat' => Time.now.to_i), options)
100
- end
101
-
102
- it 'must raise JWT::InvalidIatError when the iat value is not Numeric' do
103
- expect do
104
- Verify.verify_iat(payload.merge('iat' => 'not a number'), options)
105
- end.to raise_error JWT::InvalidIatError
106
- end
107
-
108
- it 'must raise JWT::InvalidIatError when the iat value is in the future' do
109
- expect do
110
- Verify.verify_iat(payload.merge('iat' => (iat + 120)), options)
111
- end.to raise_error JWT::InvalidIatError
112
- end
113
- end
114
-
115
- context '.verify_iss(payload, options)' do
116
- let(:iss) { 'ruby-jwt-gem' }
117
- let(:payload) { base_payload.merge('iss' => iss) }
118
-
119
- let(:invalid_token) { JWT.encode base_payload, payload[:secret] }
120
-
121
- context 'when iss is a String' do
122
- it 'must raise JWT::InvalidIssuerError when the configured issuer does not match the payload issuer' do
123
- expect do
124
- Verify.verify_iss(payload, options.merge(iss: 'mismatched-issuer'))
125
- end.to raise_error JWT::InvalidIssuerError
126
- end
127
-
128
- it 'must raise JWT::InvalidIssuerError when the payload does not include an issuer' do
129
- expect do
130
- Verify.verify_iss(base_payload, options.merge(iss: iss))
131
- end.to raise_error(JWT::InvalidIssuerError, /received <none>/)
132
- end
133
-
134
- it 'must allow a matching issuer to pass' do
135
- Verify.verify_iss(payload, options.merge(iss: iss))
136
- end
137
- end
138
- context 'when iss is an Array' do
139
- it 'must raise JWT::InvalidIssuerError when no matching issuers in array' do
140
- expect do
141
- Verify.verify_iss(payload, options.merge(iss: %w[first second]))
142
- end.to raise_error JWT::InvalidIssuerError
143
- end
144
-
145
- it 'must raise JWT::InvalidIssuerError when the payload does not include an issuer' do
146
- expect do
147
- Verify.verify_iss(base_payload, options.merge(iss: %w[first second]))
148
- end.to raise_error(JWT::InvalidIssuerError, /received <none>/)
149
- end
150
-
151
- it 'must allow an array with matching issuer to pass' do
152
- Verify.verify_iss(payload, options.merge(iss: ['first', iss, 'third']))
153
- end
154
- end
155
- end
156
-
157
- context '.verify_jti(payload, options)' do
158
- let(:payload) { base_payload.merge('jti' => 'some-random-uuid-or-whatever') }
159
-
160
- it 'must allow any jti when the verfy_jti key in the options is truthy but not a proc' do
161
- Verify.verify_jti(payload, options.merge(verify_jti: true))
162
- end
163
-
164
- it 'must raise JWT::InvalidJtiError when the jti is missing' do
165
- expect do
166
- Verify.verify_jti(base_payload, options)
167
- end.to raise_error JWT::InvalidJtiError, /missing/i
168
- end
169
-
170
- it 'must raise JWT::InvalidJtiError when the jti is an empty string' do
171
- expect do
172
- Verify.verify_jti(base_payload.merge('jti' => ' '), options)
173
- end.to raise_error JWT::InvalidJtiError, /missing/i
174
- end
175
-
176
- it 'must raise JWT::InvalidJtiError when verify_jti proc returns false' do
177
- expect do
178
- Verify.verify_jti(payload, options.merge(verify_jti: ->(_jti) { false }))
179
- end.to raise_error JWT::InvalidJtiError, /invalid/i
180
- end
181
-
182
- it 'true proc should not raise JWT::InvalidJtiError' do
183
- Verify.verify_jti(payload, options.merge(verify_jti: ->(_jti) { true }))
184
- end
185
-
186
- it 'it should not throw arguement error with 2 args' do
187
- expect do
188
- Verify.verify_jti(payload, options.merge(verify_jti: ->(_jti, pl) {
189
- true
190
- }))
191
- end.to_not raise_error
192
- end
193
- it 'should have payload as second param in proc' do
194
- Verify.verify_jti(payload, options.merge(verify_jti: ->(_jti, pl) {
195
- expect(pl).to eq(payload)
196
- }))
197
- end
198
- end
199
-
200
- context '.verify_not_before(payload, options)' do
201
- let(:payload) { base_payload.merge('nbf' => (Time.now.to_i + 5)) }
202
-
203
- it 'must raise JWT::ImmatureSignature when the nbf in the payload is in the future' do
204
- expect do
205
- Verify.verify_not_before(payload, options)
206
- end.to raise_error JWT::ImmatureSignature
207
- end
208
-
209
- it 'must allow some leeway in the token age when global leeway is configured' do
210
- Verify.verify_not_before(payload, options.merge(leeway: 10))
211
- end
212
-
213
- it 'must allow some leeway in the token age when nbf_leeway is configured' do
214
- Verify.verify_not_before(payload, options.merge(nbf_leeway: 10))
215
- end
216
- end
217
-
218
- context '.verify_sub(payload, options)' do
219
- let(:sub) { 'ruby jwt subject' }
220
-
221
- it 'must raise JWT::InvalidSubError when the subjects do not match' do
222
- expect do
223
- Verify.verify_sub(base_payload.merge('sub' => 'not-a-match'), options.merge(sub: sub))
224
- end.to raise_error JWT::InvalidSubError
225
- end
226
-
227
- it 'must allow a matching sub' do
228
- Verify.verify_sub(base_payload.merge('sub' => sub), options.merge(sub: sub))
229
- end
230
- end
231
- end
232
- end