jwt 1.5.1 → 1.5.2

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.
data/lib/jwt.rb CHANGED
@@ -7,7 +7,7 @@ require 'jwt/json'
7
7
  # JSON Web Token implementation
8
8
  #
9
9
  # Should be up to date with the latest spec:
10
- # http://self-issued.info/docs/draft-jones-json-web-token-06.html
10
+ # https://tools.ietf.org/html/rfc7519#section-4.1.5
11
11
  module JWT
12
12
  class DecodeError < StandardError; end
13
13
  class VerificationError < DecodeError; end
@@ -30,14 +30,14 @@ module JWT
30
30
  module_function
31
31
 
32
32
  def sign(algorithm, msg, key)
33
- if ['HS256', 'HS384', 'HS512'].include?(algorithm)
33
+ if %w(HS256 HS384 HS512).include?(algorithm)
34
34
  sign_hmac(algorithm, msg, key)
35
- elsif ['RS256', 'RS384', 'RS512'].include?(algorithm)
35
+ elsif %w(RS256 RS384 RS512).include?(algorithm)
36
36
  sign_rsa(algorithm, msg, key)
37
- elsif ['ES256', 'ES384', 'ES512'].include?(algorithm)
37
+ elsif %w(ES256 ES384 ES512).include?(algorithm)
38
38
  sign_ecdsa(algorithm, msg, key)
39
39
  else
40
- fail NotImplementedError.new('Unsupported signing method')
40
+ fail NotImplementedError, 'Unsupported signing method'
41
41
  end
42
42
  end
43
43
 
@@ -48,7 +48,7 @@ module JWT
48
48
  def sign_ecdsa(algorithm, msg, private_key)
49
49
  key_algorithm = NAMED_CURVES[private_key.group.curve_name]
50
50
  if algorithm != key_algorithm
51
- fail IncorrectAlgorithm.new("payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided")
51
+ fail IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided"
52
52
  end
53
53
 
54
54
  digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
@@ -62,7 +62,7 @@ module JWT
62
62
  def verify_ecdsa(algorithm, public_key, signing_input, signature)
63
63
  key_algorithm = NAMED_CURVES[public_key.group.curve_name]
64
64
  if algorithm != key_algorithm
65
- fail IncorrectAlgorithm.new("payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided")
65
+ fail IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided"
66
66
  end
67
67
 
68
68
  digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
@@ -82,7 +82,7 @@ module JWT
82
82
  Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
83
83
  end
84
84
 
85
- def encoded_header(algorithm='HS256', header_fields={})
85
+ def encoded_header(algorithm = 'HS256', header_fields = {})
86
86
  header = { 'typ' => 'JWT', 'alg' => algorithm }.merge(header_fields)
87
87
  base64url_encode(encode_json(header))
88
88
  end
@@ -100,7 +100,7 @@ module JWT
100
100
  end
101
101
  end
102
102
 
103
- def encode(payload, key, algorithm='HS256', header_fields={})
103
+ def encode(payload, key, algorithm = 'HS256', header_fields = {})
104
104
  algorithm ||= 'none'
105
105
  segments = []
106
106
  segments << encoded_header(algorithm, header_fields)
@@ -109,10 +109,10 @@ module JWT
109
109
  segments.join('.')
110
110
  end
111
111
 
112
- def raw_segments(jwt, verify=true)
112
+ def raw_segments(jwt, verify = true)
113
113
  segments = jwt.split('.')
114
114
  required_num_segments = verify ? [3] : [2, 3]
115
- fail JWT::DecodeError.new('Not enough or too many segments') unless required_num_segments.include? segments.length
115
+ fail(JWT::DecodeError, 'Not enough or too many segments') unless required_num_segments.include? segments.length
116
116
  segments
117
117
  end
118
118
 
@@ -122,7 +122,7 @@ module JWT
122
122
  [header, payload]
123
123
  end
124
124
 
125
- def decoded_segments(jwt, verify=true)
125
+ def decoded_segments(jwt, verify = true)
126
126
  header_segment, payload_segment, crypto_segment = raw_segments(jwt, verify)
127
127
  header, payload = decode_header_and_payload(header_segment, payload_segment)
128
128
  signature = base64url_decode(crypto_segment.to_s) if verify
@@ -130,21 +130,21 @@ module JWT
130
130
  [header, payload, signature, signing_input]
131
131
  end
132
132
 
133
- def decode(jwt, key=nil, verify=true, options={}, &keyfinder)
134
- fail JWT::DecodeError.new('Nil JSON web token') unless jwt
133
+ def decode(jwt, key = nil, verify = true, options = {}, &keyfinder)
134
+ fail(JWT::DecodeError, 'Nil JSON web token') unless jwt
135
135
 
136
136
  header, payload, signature, signing_input = decoded_segments(jwt, verify)
137
- fail JWT::DecodeError.new('Not enough or too many segments') unless header && payload
137
+ fail(JWT::DecodeError, 'Not enough or too many segments') unless header && payload
138
138
 
139
139
  default_options = {
140
- :verify_expiration => true,
141
- :verify_not_before => true,
142
- :verify_iss => false,
143
- :verify_iat => false,
144
- :verify_jti => false,
145
- :verify_aud => false,
146
- :verify_sub => false,
147
- :leeway => 0
140
+ verify_expiration: true,
141
+ verify_not_before: true,
142
+ verify_iss: false,
143
+ verify_iat: false,
144
+ verify_jti: false,
145
+ verify_aud: false,
146
+ verify_sub: false,
147
+ leeway: 0
148
148
  }
149
149
 
150
150
  options = default_options.merge(options)
@@ -152,36 +152,36 @@ module JWT
152
152
  if verify
153
153
  algo, key = signature_algorithm_and_key(header, key, &keyfinder)
154
154
  if options[:algorithm] && algo != options[:algorithm]
155
- fail JWT::IncorrectAlgorithm.new('Expected a different algorithm')
155
+ fail JWT::IncorrectAlgorithm, 'Expected a different algorithm'
156
156
  end
157
157
  verify_signature(algo, key, signing_input, signature)
158
158
  end
159
159
 
160
160
  if options[:verify_expiration] && payload.include?('exp')
161
- fail JWT::ExpiredSignature.new('Signature has expired') unless payload['exp'].to_i > (Time.now.to_i - options[:leeway])
161
+ fail(JWT::ExpiredSignature, 'Signature has expired') unless payload['exp'].to_i > (Time.now.to_i - options[:leeway])
162
162
  end
163
163
  if options[:verify_not_before] && payload.include?('nbf')
164
- fail JWT::ImmatureSignature.new('Signature nbf has not been reached') unless payload['nbf'].to_i < (Time.now.to_i + options[:leeway])
164
+ fail(JWT::ImmatureSignature, 'Signature nbf has not been reached') unless payload['nbf'].to_i <= (Time.now.to_i + options[:leeway])
165
165
  end
166
- if options[:verify_iss] && options['iss']
167
- fail JWT::InvalidIssuerError.new("Invalid issuer. Expected #{options['iss']}, received #{payload['iss'] || '<none>'}") unless payload['iss'].to_s == options['iss'].to_s
166
+ if options[:verify_iss] && options[:iss]
167
+ fail(JWT::InvalidIssuerError, "Invalid issuer. Expected #{options[:iss]}, received #{payload['iss'] || '<none>'}") unless payload['iss'].to_s == options[:iss].to_s
168
168
  end
169
169
  if options[:verify_iat] && payload.include?('iat')
170
- fail JWT::InvalidIatError.new('Invalid iat') unless payload['iat'].is_a?(Integer) && payload['iat'].to_i <= Time.now.to_i
170
+ fail(JWT::InvalidIatError, 'Invalid iat') unless payload['iat'].is_a?(Integer) && payload['iat'].to_i <= (Time.now.to_i + options[:leeway])
171
171
  end
172
- if options[:verify_aud] && options['aud']
173
- if payload['aud'].is_a?(Array)
174
- fail JWT::InvalidAudError.new('Invalid audience') unless payload['aud'].include?(options['aud'].to_s)
172
+ if options[:verify_aud] && options[:aud]
173
+ if payload[:aud].is_a?(Array)
174
+ fail(JWT::InvalidAudError, 'Invalid audience') unless payload['aud'].include?(options[:aud].to_s)
175
175
  else
176
- fail JWT::InvalidAudError.new("Invalid audience. Expected #{options['aud']}, received #{payload['aud'] || '<none>'}") unless payload['aud'].to_s == options['aud'].to_s
176
+ fail(JWT::InvalidAudError, "Invalid audience. Expected #{options[:aud]}, received #{payload['aud'] || '<none>'}") unless payload['aud'].to_s == options[:aud].to_s
177
177
  end
178
178
  end
179
- if options[:verify_sub] && payload.include?('sub')
180
- fail JWT::InvalidSubError.new("Invalid subject. Expected #{options['sub']}, received #{payload['sub']}") unless payload['sub'].to_s == options['sub'].to_s
179
+ if options[:verify_sub] && options.include?(:sub)
180
+ fail(JWT::InvalidSubError, "Invalid subject. Expected #{options[:sub]}, received #{payload['sub'] || '<none>'}") unless payload['sub'].to_s == options[:sub].to_s
181
181
  end
182
182
  if options[:verify_jti] && payload.include?('jti')
183
- fail JWT::InvalidJtiError.new('need iat for verify jwt id') unless payload.include?('iat')
184
- fail JWT::InvalidJtiError.new('Not a uniq jwt id') unless options['jti'].to_s == Digest::MD5.hexdigest("#{key}:#{payload['iat']}")
183
+ fail(JWT::InvalidJtiError, 'need iat for verify jwt id') unless payload.include?('iat')
184
+ fail(JWT::InvalidJtiError, 'Not a uniq jwt id') unless options[:jti].to_s == Digest::MD5.hexdigest("#{key}:#{payload['iat']}")
185
185
  end
186
186
 
187
187
  [payload, header]
@@ -193,17 +193,17 @@ module JWT
193
193
  end
194
194
 
195
195
  def verify_signature(algo, key, signing_input, signature)
196
- if ['HS256', 'HS384', 'HS512'].include?(algo)
197
- fail JWT::VerificationError.new('Signature verification failed') unless secure_compare(signature, sign_hmac(algo, signing_input, key))
198
- elsif ['RS256', 'RS384', 'RS512'].include?(algo)
199
- fail JWT::VerificationError.new('Signature verification failed') unless verify_rsa(algo, key, signing_input, signature)
200
- elsif ['ES256', 'ES384', 'ES512'].include?(algo)
201
- fail JWT::VerificationError.new('Signature verification failed') unless verify_ecdsa(algo, key, signing_input, signature)
196
+ if %w(HS256 HS384 HS512).include?(algo)
197
+ fail(JWT::VerificationError, 'Signature verification raiseed') unless secure_compare(signature, sign_hmac(algo, signing_input, key))
198
+ elsif %w(RS256 RS384 RS512).include?(algo)
199
+ fail(JWT::VerificationError, 'Signature verification raiseed') unless verify_rsa(algo, key, signing_input, signature)
200
+ elsif %w(ES256 ES384 ES512).include?(algo)
201
+ fail(JWT::VerificationError, 'Signature verification raiseed') unless verify_ecdsa(algo, key, signing_input, signature)
202
202
  else
203
- fail JWT::VerificationError.new('Algorithm not supported')
203
+ fail JWT::VerificationError, 'Algorithm not supported'
204
204
  end
205
205
  rescue OpenSSL::PKey::PKeyError
206
- raise JWT::VerificationError.new('Signature verification failed')
206
+ raise JWT::VerificationError, 'Signature verification raised'
207
207
  ensure
208
208
  OpenSSL.errors.clear
209
209
  end
@@ -8,7 +8,7 @@ module JWT
8
8
  def decode_json(encoded)
9
9
  JSON.parse(encoded)
10
10
  rescue JSON::ParserError
11
- raise JWT::DecodeError.new('Invalid segment encoding')
11
+ raise JWT::DecodeError, 'Invalid segment encoding'
12
12
  end
13
13
 
14
14
  def encode_json(raw)
@@ -21,7 +21,7 @@ module JWT
21
21
  def decode_json(encoded)
22
22
  MultiJson.decode(encoded)
23
23
  rescue MultiJson::LoadError
24
- raise JWT::DecodeError.new('Invalid segment encoding')
24
+ raise JWT::DecodeError, 'Invalid segment encoding'
25
25
  end
26
26
 
27
27
  def encode_json(raw)
@@ -1,463 +1,412 @@
1
- # encoding: utf-8
2
- require 'helper'
1
+ require 'spec_helper'
2
+ require 'jwt'
3
3
 
4
4
  describe JWT do
5
- before do
6
- @payload = { 'foo' => 'bar', 'exp' => Time.now.to_i + 1, 'nbf' => Time.now.to_i - 1 }
5
+ let(:payload) { { 'user_id' => 'some@user.tld' } }
6
+
7
+ let :data do
8
+ {
9
+ :secret => 'My$ecretK3y',
10
+ :rsa_private => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-private.pem'))),
11
+ :rsa_public => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-public.pem'))),
12
+ :wrong_rsa_private => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-wrong-public.pem'))),
13
+ :wrong_rsa_public => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-wrong-public.pem'))),
14
+ 'ES256_private' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec256-private.pem'))),
15
+ 'ES256_public' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec256-public.pem'))),
16
+ 'ES384_private' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec384-private.pem'))),
17
+ 'ES384_public' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec384-public.pem'))),
18
+ 'ES512_private' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec512-private.pem'))),
19
+ 'ES512_public' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec512-public.pem'))),
20
+ 'NONE' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.',
21
+ 'HS256' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.tCGvlClld0lbQ3NZaH8y53n5RSBr3zlS4Oy5bXqvzZQ',
22
+ 'HS384' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.sj1gc01SawlJSrPZgmveifJ8CzZRYAWjejWm4FRaGaAISESJ9Ncf12fCz2vHrITm',
23
+ 'HS512' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.isjhsWMZpRQOWw6LKtlY4L6tMDNkLr0qZ3bQe_xRFXWhzVvJlkclTbLVa1J6Dlj2WyZ_I1jEobTaFMDoXPzwWg',
24
+ 'RS256' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.u82QrhjZTtwve5akvfWS_4LPywbkb1Yp0nUwZJWtTW0ID7dY9rRiQF5KGj2UDLZotqRlUjyNQgE_hB5BBzICDQdCjQHQoYWE5n_D2wV4PMu7Qg3FVKoBFbf8ee6irodu10fgYxpUIZtvbWw52_6k6A9IoSLSzx_lCcxoVGdW90dUuKhBcZkDtY5WNuQg7MiDthupSL1-V4Y1jmT_7o8tLNGFiocyZfGNw4yGpEOGNvD5WePNit0xsnbj6dEquovUvSFKsMaQXp2PVDEkLOiLMcyk0RrHqrHw2eNSCquWTH8PhX5Up-CVmjQM5zF9ibkaiq8NyPtsy-7rgtbyVMqXBQ',
25
+ 'RS384' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.2_jPwOsUWJ-3r6lXMdJGPdhLNJQSSEmY2mrDXCwNJk-2YhMIqKAzJJCbyso_A1hS7BVkXmHt54RCcNJXroZBOgmGavCcYTPMaT6sCvVVvJJ_wn7jzKHNAJfL5nWeynTQIBWmL-m_v9QpZAgPALdeqjPRv4JHePZm23kvrUgQOxef2ldXv1l6IB3zfF72uEbk9T5pKBvgeeeQ46xm_HtkpXqMdqcTHawUXeXhuiWxuWfy9pAvhm8ivxwJhiQ15-sQNBlS9lG1_gQz1xaZ_Ou_n1nhNfGwpK5HeS0AgmqsqyCOvaGHeAuAOPZ_dSC3cFKu2AP7kc6_AKBgwJzh4agkXg',
26
+ 'RS512' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.abwof7BqTvuLkN69OhEuFTP7vjGzfvAvooQdwIRne_a88MsjCq31n4UPvyIlY9_8u69rpU79RbMsrq_UZ6L85zP83EcyYI-HOfFZgYDAL3DJ7biBD99JTzyOsH_2i_E6yCkevjEX6uL_Am_C7jpWyePJQkYzTFni6mW4W1T9UobiVGA1tIZ-XOJDPHHxZkGu6W8lKW0UCsr9Ge2SCSlTs_LDSOa34gqMC5GP89unhLqSMqEMJ_Nm6Rj0rnmk87wBZM-b04LLteWuEU59QDNa4nMTjfXW74U4hX9n5EECDPQdQMecgxlUbFunAfZaoNzP4m7H4vux2FzYkjkXhdqnnw',
27
+ 'ES256' => '',
28
+ 'ES384' => '',
29
+ 'ES512' => ''
30
+ }
31
+ end
32
+
33
+ after(:each) do
34
+ expect(OpenSSL.errors).to be_empty
7
35
  end
8
36
 
9
- it 'encodes and decodes JWTs' do
10
- secret = 'secret'
11
- jwt = JWT.encode(@payload, secret)
12
- decoded_payload = JWT.decode(jwt, secret)
13
- expect(decoded_payload).to include(@payload)
14
- end
37
+ context 'alg: NONE' do
38
+ let(:alg) { 'none' }
15
39
 
16
- it 'encodes and decodes JWTs for RSA signatures' do
17
- private_key = OpenSSL::PKey::RSA.generate(512)
18
- jwt = JWT.encode(@payload, private_key, 'RS256')
19
- decoded_payload = JWT.decode(jwt, private_key.public_key)
20
- expect(decoded_payload).to include(@payload)
21
- end
40
+ it 'should generate a valid token' do
41
+ token = JWT.encode payload, nil, alg
22
42
 
23
- it 'encodes and decodes JWTs for ECDSA P-256 signatures' do
24
- private_key = OpenSSL::PKey::EC.new('prime256v1')
25
- private_key.generate_key
26
- public_key = OpenSSL::PKey::EC.new(private_key)
27
- public_key.private_key = nil
28
- jwt = JWT.encode(@payload, private_key, 'ES256')
29
- decoded_payload = JWT.decode(jwt, public_key)
30
- expect(decoded_payload).to include(@payload)
31
- end
43
+ expect(token).to eq data['NONE']
44
+ end
32
45
 
33
- it 'encodes and decodes JWTs for ECDSA P-384 signatures' do
34
- private_key = OpenSSL::PKey::EC.new('secp384r1')
35
- private_key.generate_key
36
- public_key = OpenSSL::PKey::EC.new(private_key)
37
- public_key.private_key = nil
38
- jwt = JWT.encode(@payload, private_key, 'ES384')
39
- decoded_payload = JWT.decode(jwt, public_key)
40
- expect(decoded_payload).to include(@payload)
41
- end
46
+ it 'should decode a valid token' do
47
+ jwt_payload, header = JWT.decode data['NONE'], nil, false
42
48
 
43
- it 'encodes and decodes JWTs for ECDSA P-521 signatures' do
44
- private_key = OpenSSL::PKey::EC.new('secp521r1')
45
- private_key.generate_key
46
- public_key = OpenSSL::PKey::EC.new(private_key)
47
- public_key.private_key = nil
48
- jwt = JWT.encode(@payload, private_key, 'ES512')
49
- decoded_payload = JWT.decode(jwt, public_key)
50
- expect(decoded_payload).to include(@payload)
49
+ expect(header['alg']).to eq alg
50
+ expect(jwt_payload).to eq payload
51
+ end
51
52
  end
52
53
 
53
- it 'encodes and decodes JWTs with custom header fields' do
54
- private_key = OpenSSL::PKey::RSA.generate(512)
55
- jwt = JWT.encode(@payload, private_key, 'RS256', 'kid' => 'default')
56
- decoded_payload = JWT.decode(jwt) do |header|
57
- expect(header['kid']).to eq('default')
58
- private_key.public_key
54
+ %w(HS256 HS384 HS512).each do |alg|
55
+ context "alg: #{alg}" do
56
+ it 'should generate a valid token' do
57
+ token = JWT.encode payload, data[:secret], alg
58
+
59
+ expect(token).to eq data[alg]
60
+ end
61
+
62
+ it 'should decode a valid token' do
63
+ jwt_payload, header = JWT.decode data[alg], data[:secret]
64
+
65
+ expect(header['alg']).to eq alg
66
+ expect(jwt_payload).to eq payload
67
+ end
68
+
69
+ it 'wrong secret should raise JWT::DecodeError' do
70
+ expect do
71
+ JWT.decode data[alg], 'wrong_secret'
72
+ end.to raise_error JWT::DecodeError
73
+ end
74
+
75
+ it 'wrong secret and verify = false should not raise JWT::DecodeError' do
76
+ expect do
77
+ JWT.decode data[alg], 'wrong_secret', false
78
+ end.not_to raise_error
79
+ end
59
80
  end
60
- expect(decoded_payload).to include(@payload)
61
81
  end
62
82
 
63
- it 'raises encode exception when ECDSA algorithm does not match key' do
64
- private_key = OpenSSL::PKey::EC.new('prime256v1')
65
- private_key.generate_key
66
- expect do
67
- JWT.encode(@payload, private_key, 'ES512')
68
- end.to raise_error(JWT::IncorrectAlgorithm, 'payload algorithm is ES512 but ES256 signing key was provided')
69
- end
83
+ %w(RS256 RS384 RS512).each do |alg|
84
+ context "alg: #{alg}" do
85
+ it 'should generate a valid token' do
86
+ token = JWT.encode payload, data[:rsa_private], alg
70
87
 
71
- it 'decodes valid JWTs' do
72
- example_payload = { 'hello' => 'world' }
73
- example_secret = 'secret'
74
- example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
75
- decoded_payload = JWT.decode(example_jwt, example_secret)
76
- expect(decoded_payload).to include(example_payload)
77
- end
88
+ expect(token).to eq data[alg]
89
+ end
78
90
 
79
- it 'decodes valid ES512 JWTs' do
80
- example_payload = { 'hello' => 'world' }
81
- example_jwt = 'eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJoZWxsbyI6IndvcmxkIn0.AQx1MqdTni6KuzfOoedg2-7NUiwe-b88SWbdmviz40GTwrM0Mybp1i1tVtmTSQ91oEXGXBdtwsN6yalzP9J-sp2YATX_Tv4h-BednbdSvYxZsYnUoZ--ZUdL10t7g8Yt3y9hdY_diOjIptcha6ajX8yzkDGYG42iSe3f5LywSuD6FO5c'
82
- pubkey_pem = "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAcpkss6wI7PPlxj3t7A1RqMH3nvL4\nL5Tzxze/XeeYZnHqxiX+gle70DlGRMqqOq+PJ6RYX7vK0PJFdiAIXlyPQq0B3KaU\ne86IvFeQSFrJdCc0K8NfiH2G1loIk3fiR+YLqlXk6FAeKtpXJKxR1pCQCAM+vBCs\nmZudf1zCUZ8/4eodlHU=\n-----END PUBLIC KEY-----"
83
- pubkey = OpenSSL::PKey::EC.new pubkey_pem
84
- decoded_payload = JWT.decode(example_jwt, pubkey)
85
- expect(decoded_payload).to include(example_payload)
86
- end
91
+ it 'should decode a valid token' do
92
+ jwt_payload, header = JWT.decode data[alg], data[:rsa_public]
87
93
 
88
- it 'decodes valid JWTs with iss' do
89
- example_payload = { 'hello' => 'world', 'iss' => 'jwtiss' }
90
- example_secret = 'secret'
91
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaXNzIjoiand0aXNzIn0.nTZkyYfpGUyKULaj45lXw_1gXXjHvGW4h5V7okHdUqQ'
92
- decoded_payload = JWT.decode(example_jwt, example_secret, true, 'iss' => 'jwtiss')
93
- expect(decoded_payload).to include(example_payload)
94
- end
94
+ expect(header['alg']).to eq alg
95
+ expect(jwt_payload).to eq payload
96
+ end
95
97
 
96
- context 'issuer claim verifications' do
97
- it 'raises invalid issuer when "iss" claim does not match' do
98
- example_secret = 'secret'
98
+ it 'wrong key should raise JWT::DecodeError' do
99
+ key = OpenSSL::PKey.read File.read(File.join(CERT_PATH, 'rsa-2048-wrong-public.pem'))
99
100
 
100
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaXNzIjoiand0aXNzIn0.nTZkyYfpGUyKULaj45lXw_1gXXjHvGW4h5V7okHdUqQ'
101
- expect { JWT.decode(example_jwt, example_secret, true, :verify_iss => true, 'iss' => 'jwt_iss') }.to raise_error(JWT::InvalidIssuerError, /Expected jwt_iss, received jwtiss/)
102
- end
101
+ expect do
102
+ JWT.decode data[alg], key
103
+ end.to raise_error JWT::DecodeError
104
+ end
103
105
 
104
- it 'raises invalid issuer when "iss" claim is missing in payload' do
105
- example_secret = 'secret'
106
+ it 'wrong key and verify = false should not raise JWT::DecodeError' do
107
+ key = OpenSSL::PKey.read File.read(File.join(CERT_PATH, 'rsa-2048-wrong-public.pem'))
106
108
 
107
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIn0.bqxXg9VwcbXKoiWtp-osd0WKPX307RjcN7EuXbdq-CE'
108
- expect { JWT.decode(example_jwt, example_secret, true, :verify_iss => true, 'iss' => 'jwt_iss') }.to raise_error(JWT::InvalidIssuerError, /received <none>/)
109
+ expect do
110
+ JWT.decode data[alg], key, false
111
+ end.not_to raise_error
112
+ end
109
113
  end
114
+ end
110
115
 
111
- it 'does not raise invalid issuer when verify_iss is set to false (default option)' do
112
- example_secret = 'secret'
116
+ %w(ES256 ES384 ES512).each do |alg|
117
+ context "alg: #{alg}" do
118
+ before(:each) do
119
+ data[alg] = JWT.encode payload, data["#{alg}_private"], alg
120
+ end
113
121
 
114
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaXNzIjoiand0aXNzIn0.nTZkyYfpGUyKULaj45lXw_1gXXjHvGW4h5V7okHdUqQ'
115
- expect { JWT.decode(example_jwt, example_secret, true, 'iss' => 'jwt_iss') }.not_to raise_error
116
- end
122
+ let(:wrong_key) { OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec256-wrong-public.pem'))) }
117
123
 
118
- it 'does not raise invalid issuer when correct "iss" is in payload' do
119
- example_secret = 'secret'
124
+ it 'should generate a valid token' do
125
+ jwt_payload, header = JWT.decode data[alg], data["#{alg}_public"]
120
126
 
121
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaXNzIjoiand0X2lzcyJ9.mwbyRJJZJR1C5lBt8WOLg0ZMuwP9VGDf5HiQtFhd-eA'
122
- expect { JWT.decode(example_jwt, example_secret, true, :verify_iss => true, 'iss' => 'jwt_iss') }.not_to raise_error
127
+ expect(header['alg']).to eq alg
128
+ expect(jwt_payload).to eq payload
129
+ end
130
+
131
+ it 'should decode a valid token' do
132
+ jwt_payload, header = JWT.decode data[alg], data["#{alg}_public"]
133
+
134
+ expect(header['alg']).to eq alg
135
+ expect(jwt_payload).to eq payload
136
+ end
137
+
138
+ it 'wrong key should raise JWT::DecodeError' do
139
+ expect do
140
+ JWT.decode data[alg], wrong_key
141
+ end.to raise_error JWT::DecodeError
142
+ end
143
+
144
+ it 'wrong key and verify = false should not raise JWT::DecodeError' do
145
+ expect do
146
+ JWT.decode data[alg], wrong_key, false
147
+ end.not_to raise_error
148
+ end
123
149
  end
124
150
  end
125
151
 
126
- it 'decodes valid JWTs with iat' do
127
- example_payload = { 'hello' => 'world', 'iat' => 1425917209 }
128
- example_secret = 'secret'
129
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5fQ.m4F-Ugo7aLnLunBBO3BeDidyWMx8T9eoJz6FW2rgQhU'
130
- decoded_payload = JWT.decode(example_jwt, example_secret, true, 'iat' => true)
131
- expect(decoded_payload).to include(example_payload)
132
- end
152
+ context 'Invalid' do
153
+ it 'algorithm should raise NotImplementedError' do
154
+ expect do
155
+ JWT.encode payload, 'secret', 'HS255'
156
+ end.to raise_error NotImplementedError
157
+ end
133
158
 
134
- it 'raises decode exception when iat is invalid' do
135
- # example_payload = {'hello' => 'world', 'iat' => 'abc'}
136
- example_secret = 'secret'
137
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoiMTQyNTkxNzIwOSJ9.Mn_vk61xWjIhbXFqAB0nFmNkDiCmfzUgl_LaCKRT6S8'
138
- expect { JWT.decode(example_jwt, example_secret, true, :verify_iat => true, 'iat' => 1425917209) }.to raise_error(JWT::InvalidIatError)
139
- end
159
+ it 'ECDSA curve_name should raise JWT::IncorrectAlgorithm' do
160
+ key = OpenSSL::PKey::EC.new 'secp256k1'
161
+ key.generate_key
140
162
 
141
- it 'decodes valid JWTs with jti' do
142
- example_payload = { 'hello' => 'world', 'iat' => 1425917209, 'jti' => Digest::MD5.hexdigest('secret:1425917209') }
143
- example_secret = 'secret'
144
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5LCJqdGkiOiI1NWM3NzZlMjFmN2NiZDg3OWMwNmZhYzAxOGRhYzQwMiJ9.ET0hb-VTUOL3M22oG13ofzvGPLMAncbF8rdNDIqo8tg'
145
- decoded_payload = JWT.decode(example_jwt, example_secret, true, 'jti' => Digest::MD5.hexdigest('secret:1425917209'))
146
- expect(decoded_payload).to include(example_payload)
147
- end
163
+ expect do
164
+ JWT.encode payload, key, 'ES256'
165
+ end.to raise_error JWT::IncorrectAlgorithm
148
166
 
149
- it 'raises decode exception when jti is invalid' do
150
- # example_payload = {'hello' => 'world', 'iat' => 1425917209, 'jti' => Digest::MD5.hexdigest('secret:1425917209')}
151
- example_secret = 'secret'
152
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5LCJqdGkiOiI1NWM3NzZlMjFmN2NiZDg3OWMwNmZhYzAxOGRhYzQwMiJ9.ET0hb-VTUOL3M22oG13ofzvGPLMAncbF8rdNDIqo8tg'
153
- expect { JWT.decode(example_jwt, example_secret, true, :verify_jti => true, 'jti' => Digest::MD5.hexdigest('secret:1425922032')) }.to raise_error(JWT::InvalidJtiError)
154
- # expect{ JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::InvalidJtiError)
155
- end
167
+ token = JWT.encode payload, data['ES256_private'], 'ES256'
168
+ key.private_key = nil
156
169
 
157
- it 'raises decode exception when jti without iat' do
158
- # example_payload = {'hello' => 'world', 'jti' => Digest::MD5.hexdigest('secret:1425917209')}
159
- example_secret = 'secret'
160
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwianRpIjoiNTVjNzc2ZTIxZjdjYmQ4NzljMDZmYWMwMThkYWM0MDIifQ.n0foJCnCM_-_xUvG_TOmR9mYpL2y0UqZOD_gv33djeE'
161
- expect { JWT.decode(example_jwt, example_secret, true, :verify_jti => true, 'jti' => Digest::MD5.hexdigest('secret:1425922032')) }.to raise_error(JWT::InvalidJtiError)
170
+ expect do
171
+ JWT.decode token, key
172
+ end.to raise_error JWT::IncorrectAlgorithm
173
+ end
162
174
  end
163
175
 
164
- context 'aud claim verifications' do
165
- it 'decodes valid JWTs with aud' do
166
- example_payload = { 'hello' => 'world', 'aud' => 'url:pnd' }
167
- example_payload2 = { 'hello' => 'world', 'aud' => ['url:pnd', 'aud:yes'] }
168
- example_secret = 'secret'
169
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiYXVkIjoidXJsOnBuZCJ9._gT5veUtNiZD7wLEC6Gd0-nkQV3cl1z8G0zXq8qcd-8'
170
- example_jwt2 = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiYXVkIjpbInVybDpwbmQiLCJhdWQ6eWVzIl19.qNPNcT4X9B5uI91rIwbW2bIPTsp8wbRYW3jkZkrmqbQ'
171
- decoded_payload = JWT.decode(example_jwt, example_secret, true, :verify_aud => true, 'aud' => 'url:pnd')
172
- decoded_payload2 = JWT.decode(example_jwt2, example_secret, true, :verify_aud => true, 'aud' => 'url:pnd')
173
- expect(decoded_payload).to include(example_payload)
174
- expect(decoded_payload2).to include(example_payload2)
175
- end
176
+ context 'Verify' do
177
+ context 'algorithm' do
178
+ it 'should raise JWT::IncorrectAlgorithm on missmatch' do
179
+ token = JWT.encode payload, data[:secret], 'HS512'
180
+
181
+ expect do
182
+ JWT.decode token, data[:secret], true, algorithm: 'HS384'
183
+ end.to raise_error JWT::IncorrectAlgorithm
176
184
 
177
- it 'raises deode exception when aud is invalid' do
178
- # example_payload = {'hello' => 'world', 'aud' => 'url:pnd'}
179
- example_secret = 'secret'
180
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiYXVkIjoidXJsOnBuZCJ9._gT5veUtNiZD7wLEC6Gd0-nkQV3cl1z8G0zXq8qcd-8'
181
- expect { JWT.decode(example_jwt, example_secret, true, :verify_aud => true, 'aud' => 'wrong:aud') }.to raise_error(JWT::InvalidAudError)
185
+ expect do
186
+ JWT.decode token, data[:secret], true, algorithm: 'HS512'
187
+ end.not_to raise_error
188
+ end
182
189
  end
183
190
 
184
- it 'raises deode exception when aud is missing' do
185
- # JWT.encode('hello' => 'world', 'secret')
186
- example_secret = 'secret'
187
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIn0.bqxXg9VwcbXKoiWtp-osd0WKPX307RjcN7EuXbdq-CE'
188
- expect { JWT.decode(example_jwt, example_secret, true, :verify_aud => true, 'aud' => 'url:pnd') }.to raise_error(JWT::InvalidAudError)
191
+ context 'expiration claim' do
192
+ let(:exp) { Time.now.to_i - 5 }
193
+ let(:leeway) { 10 }
194
+
195
+ let :token do
196
+ payload.merge!(exp: exp)
197
+
198
+ JWT.encode payload, data[:secret]
199
+ end
200
+
201
+ it 'old token should raise JWT::ExpiredSignature' do
202
+ expect do
203
+ JWT.decode token, data[:secret]
204
+ end.to raise_error JWT::ExpiredSignature
205
+ end
206
+
207
+ it 'should handle leeway' do
208
+ expect do
209
+ JWT.decode token, data[:secret], true, leeway: leeway
210
+ end.not_to raise_error
211
+ end
189
212
  end
190
- end
191
213
 
192
- it 'decodes valid JWTs with sub' do
193
- example_payload = { 'hello' => 'world', 'sub' => 'subject' }
194
- example_secret = 'secret'
195
- example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwic3ViIjoic3ViamVjdCJ9.QUnNVZm4SPB4vP2zY9m1LoUSOx-5oGXBhj7R89D_UtA'
196
- decoded_payload = JWT.decode(example_jwt, example_secret, true, 'sub' => 'subject')
197
- expect(decoded_payload).to include(example_payload)
198
- end
214
+ context 'not before claim' do
215
+ let(:nbf) { Time.now.to_i + 5 }
216
+ let(:leeway) { 10 }
199
217
 
200
- it 'raise decode exception when the sub is invalid'
218
+ let :token do
219
+ payload.merge!(nbf: nbf)
201
220
 
202
- it 'raises decode exception when the token is invalid' do
203
- example_secret = 'secret'
204
- # Same as above exmaple with some random bytes replaced
205
- example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHiMomlwIjogIkJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
206
- expect { JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::DecodeError)
207
- end
221
+ JWT.encode payload, data[:secret]
222
+ end
208
223
 
209
- it 'raises verification exception with wrong hmac key' do
210
- right_secret = 'foo'
211
- bad_secret = 'bar'
212
- jwt_message = JWT.encode(@payload, right_secret, 'HS256')
213
- expect { JWT.decode(jwt_message, bad_secret) }.to raise_error(JWT::VerificationError)
214
- end
224
+ it 'future token should raise JWT::ImmatureSignature' do
225
+ expect do
226
+ JWT.decode token, data[:secret]
227
+ end.to raise_error JWT::ImmatureSignature
228
+ end
215
229
 
216
- it 'raises decode exception when ECDSA algorithm does not match key' do
217
- right_private_key = OpenSSL::PKey::EC.new('prime256v1')
218
- right_private_key.generate_key
219
- right_public_key = OpenSSL::PKey::EC.new(right_private_key)
220
- right_public_key.private_key = nil
221
- bad_private_key = OpenSSL::PKey::EC.new('secp384r1')
222
- bad_private_key.generate_key
223
- bad_public_key = OpenSSL::PKey::EC.new(bad_private_key)
224
- bad_public_key.private_key = nil
225
- jwt = JWT.encode(@payload, right_private_key, 'ES256')
226
- expect do
227
- JWT.decode(jwt, bad_public_key)
228
- end.to raise_error(JWT::IncorrectAlgorithm, 'payload algorithm is ES256 but ES384 verification key was provided')
229
- end
230
+ it 'should handle leeway' do
231
+ expect do
232
+ JWT.decode token, data[:secret], true, leeway: leeway
233
+ end.not_to raise_error
234
+ end
235
+ end
230
236
 
231
- it 'raises verification exception with wrong rsa key' do
232
- right_private_key = OpenSSL::PKey::RSA.generate(512)
233
- bad_private_key = OpenSSL::PKey::RSA.generate(512)
234
- jwt = JWT.encode(@payload, right_private_key, 'RS256')
235
- expect { JWT.decode(jwt, bad_private_key.public_key) }.to raise_error(JWT::VerificationError)
236
- end
237
+ context 'issuer claim' do
238
+ let(:iss) { 'ruby-jwt-gem' }
239
+ let(:invalid_token) { JWT.encode payload, data[:secret] }
237
240
 
238
- it 'raises verification exception with wrong ECDSA key' do
239
- right_private_key = OpenSSL::PKey::EC.new('prime256v1')
240
- right_private_key.generate_key
241
- bad_private_key = OpenSSL::PKey::EC.new('prime256v1')
242
- bad_private_key.generate_key
243
- bad_public_key = OpenSSL::PKey::EC.new(bad_private_key)
244
- bad_public_key.private_key = nil
245
- jwt = JWT.encode(@payload, right_private_key, 'ES256')
246
- expect { JWT.decode(jwt, bad_public_key) }.to raise_error(JWT::VerificationError)
247
- end
241
+ let :token do
242
+ iss_payload = payload.merge(iss: iss)
243
+ JWT.encode iss_payload, data[:secret]
244
+ end
248
245
 
249
- it 'raises decode exception with invalid signature' do
250
- example_secret = 'secret'
251
- example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.'
252
- expect { JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::DecodeError)
253
- end
246
+ it 'if verify_iss is set to false (default option) should not raise JWT::InvalidIssuerError' do
247
+ expect do
248
+ JWT.decode token, data[:secret], true, iss: iss
249
+ end.not_to raise_error
250
+ end
254
251
 
255
- it 'raises decode exception with nonexistent header' do
256
- expect { JWT.decode('..stuff') }.to raise_error(JWT::DecodeError)
257
- end
252
+ it 'invalid iss should raise JWT::InvalidIssuerError' do
253
+ expect do
254
+ JWT.decode token, data[:secret], true, iss: 'wrong-issuer', verify_iss: true
255
+ end.to raise_error JWT::InvalidIssuerError
256
+ end
258
257
 
259
- it 'raises decode exception with nonexistent payload' do
260
- expect { JWT.decode('eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9..stuff') }.to raise_error(JWT::DecodeError)
261
- end
258
+ it 'with missing iss claim should raise JWT::InvalidIssuerError' do
259
+ missing_iss_claim_token = JWT.encode payload, data[:secret]
262
260
 
263
- it 'raises decode exception with nil jwt' do
264
- expect { JWT.decode(nil) }.to raise_error(JWT::DecodeError)
265
- end
261
+ expect do
262
+ JWT.decode missing_iss_claim_token, data[:secret], true, verify_iss: true, iss: iss
263
+ end.to raise_error(JWT::InvalidIssuerError, /received <none>/)
264
+ end
266
265
 
267
- it 'allows decoding without key' do
268
- right_secret = 'foo'
269
- bad_secret = 'bar'
270
- jwt = JWT.encode(@payload, right_secret)
271
- decoded_payload = JWT.decode(jwt, bad_secret, false)
272
- expect(decoded_payload).to include(@payload)
273
- end
266
+ it 'valid iss should not raise JWT::InvalidIssuerError' do
267
+ expect do
268
+ JWT.decode token, data[:secret], true, iss: iss, verify_iss: true
269
+ end.not_to raise_error
270
+ end
271
+ end
274
272
 
275
- it 'checks the key when verify is truthy' do
276
- right_secret = 'foo'
277
- bad_secret = 'bar'
278
- jwt = JWT.encode(@payload, right_secret)
279
- verify = 'yes' =~ /^y/i
280
- expect { JWT.decode(jwt, bad_secret, verify) }.to raise_error(JWT::DecodeError)
281
- end
273
+ context 'issued at claim' do
274
+ let(:iat) { Time.now.to_i }
275
+ let(:new_payload) { payload.merge(iat: iat) }
276
+ let(:token) { JWT.encode new_payload, data[:secret] }
277
+ let(:invalid_token) { JWT.encode new_payload.merge('iat' => iat + 60), data[:secret] }
278
+ let(:leeway) { 30 }
279
+
280
+ it 'invalid iat should raise JWT::InvalidIatError' do
281
+ expect do
282
+ JWT.decode invalid_token, data[:secret], true, verify_iat: true
283
+ end.to raise_error JWT::InvalidIatError
284
+ end
282
285
 
283
- it 'raises exception on unsupported crypto algorithm' do
284
- expect { JWT.encode(@payload, 'secret', 'HS1024') }.to raise_error(NotImplementedError)
285
- end
286
+ it 'should accept leeway' do
287
+ expect do
288
+ JWT.decode invalid_token, data[:secret], true, verify_iat: true, leeway: 70
289
+ end.to_not raise_error
290
+ end
286
291
 
287
- it 'raises exception when decoded with a different algorithm than it was encoded with' do
288
- jwt = JWT.encode(@payload, 'foo', 'HS384')
289
- expect { JWT.decode(jwt, 'foo', true, :algorithm => 'HS512') }.to raise_error(JWT::IncorrectAlgorithm)
290
- end
292
+ it 'valid iat should not raise JWT::InvalidIatError' do
293
+ expect do
294
+ JWT.decode token, data[:secret], true, verify_iat: true
295
+ end.to_not raise_error
296
+ end
297
+ end
291
298
 
292
- it 'does not raise exception when encoded with the expected algorithm' do
293
- jwt = JWT.encode(@payload, 'foo', 'HS512')
294
- JWT.decode(jwt, 'foo', true, :algorithm => 'HS512')
295
- end
299
+ context 'audience claim' do
300
+ let(:simple_aud) { 'ruby-jwt-audience' }
301
+ let(:array_aud) { %w(ruby-jwt-aud test-aud ruby-ruby-ruby) }
296
302
 
297
- it 'encodes and decodes plaintext JWTs' do
298
- jwt = JWT.encode(@payload, nil, nil)
299
- expect(jwt.split('.').length).to eq(2)
300
- decoded_payload = JWT.decode(jwt, nil, nil)
301
- expect(decoded_payload).to include(@payload)
302
- end
303
+ let :simple_token do
304
+ new_payload = payload.merge('aud' => simple_aud)
305
+ JWT.encode new_payload, data[:secret]
306
+ end
303
307
 
304
- it 'requires a signature segment when verify is truthy' do
305
- jwt = JWT.encode(@payload, nil, nil)
306
- expect(jwt.split('.').length).to eq(2)
307
- expect { JWT.decode(jwt, nil, true) }.to raise_error(JWT::DecodeError)
308
- end
308
+ let :array_token do
309
+ new_payload = payload.merge('aud' => array_aud)
310
+ JWT.encode new_payload, data[:secret]
311
+ end
309
312
 
310
- it 'does not use == to compare digests' do
311
- secret = 'secret'
312
- jwt = JWT.encode(@payload, secret)
313
- crypto_segment = jwt.split('.').last
313
+ it 'invalid aud should raise JWT::InvalidAudError' do
314
+ expect do
315
+ JWT.decode simple_token, data[:secret], true, aud: 'wrong audience', verify_aud: true
316
+ end.to raise_error JWT::InvalidAudError
314
317
 
315
- signature = JWT.base64url_decode(crypto_segment)
316
- expect(signature).not_to receive('==')
317
- expect(JWT).to receive(:base64url_decode).with(crypto_segment).once.and_return(signature)
318
- expect(JWT).to receive(:base64url_decode).at_least(:once).and_call_original
318
+ expect do
319
+ JWT.decode array_token, data[:secret], true, aud: %w(wrong audience), verify_aud: true
320
+ end.to raise_error JWT::InvalidAudError
321
+ end
319
322
 
320
- JWT.decode(jwt, secret)
321
- end
323
+ it 'valid aud should not raise JWT::InvalidAudError' do
324
+ expect do
325
+ JWT.decode simple_token, data[:secret], true, 'aud' => simple_aud, :verify_aud => true
326
+ end.to_not raise_error
322
327
 
323
- it 'raises error when expired' do
324
- expired_payload = @payload.clone
325
- expired_payload['exp'] = Time.now.to_i - 1
326
- secret = 'secret'
327
- jwt = JWT.encode(expired_payload, secret)
328
- expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ExpiredSignature)
329
- end
328
+ expect do
329
+ JWT.decode array_token, data[:secret], true, 'aud' => array_aud.first, :verify_aud => true
330
+ end.to_not raise_error
331
+ end
332
+ end
330
333
 
331
- it 'raise ExpiredSignature even when exp claims is a string' do
332
- expired_payload = @payload.clone
333
- expired_payload['exp'] = (Time.now.to_i).to_s
334
- secret = 'secret'
335
- jwt = JWT.encode(expired_payload, secret)
336
- expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ExpiredSignature)
337
- end
334
+ context 'subject claim' do
335
+ let(:sub) { 'ruby jwt subject' }
338
336
 
339
- it 'performs normal decode with skipped expiration check' do
340
- expired_payload = @payload.clone
341
- expired_payload['exp'] = Time.now.to_i - 1
342
- secret = 'secret'
343
- jwt = JWT.encode(expired_payload, secret)
344
- decoded_payload = JWT.decode(jwt, secret, true, :verify_expiration => false)
345
- expect(decoded_payload).to include(expired_payload)
346
- end
337
+ let :token do
338
+ new_payload = payload.merge('sub' => sub)
339
+ JWT.encode new_payload, data[:secret]
340
+ end
347
341
 
348
- it 'performs normal decode using leeway' do
349
- expired_payload = @payload.clone
350
- expired_payload['exp'] = Time.now.to_i - 2
351
- secret = 'secret'
352
- jwt = JWT.encode(expired_payload, secret)
353
- decoded_payload = JWT.decode(jwt, secret, true, :leeway => 3)
354
- expect(decoded_payload).to include(expired_payload)
355
- end
342
+ let :invalid_token do
343
+ new_payload = payload.merge('sub' => 'we are not the druids you are looking for')
344
+ JWT.encode new_payload, data[:secret]
345
+ end
356
346
 
357
- it 'raises error when before nbf' do
358
- immature_payload = @payload.clone
359
- immature_payload['nbf'] = Time.now.to_i + 1
360
- secret = 'secret'
361
- jwt = JWT.encode(immature_payload, secret)
362
- expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ImmatureSignature)
363
- end
347
+ it 'invalid sub should raise JWT::InvalidSubError' do
348
+ expect do
349
+ JWT.decode invalid_token, data[:secret], true, sub: sub, verify_sub: true
350
+ end.to raise_error JWT::InvalidSubError
351
+ end
364
352
 
365
- it 'doesnt raise error when after nbf' do
366
- mature_payload = @payload.clone
367
- secret = 'secret'
368
- jwt = JWT.encode(mature_payload, secret)
369
- decoded_payload = JWT.decode(jwt, secret, true, :verify_expiration => false)
370
- expect(decoded_payload).to include(mature_payload)
371
- end
353
+ it 'valid sub should not raise JWT::InvalidSubError' do
354
+ expect do
355
+ JWT.decode token, data[:secret], true, 'sub' => sub, :verify_sub => true
356
+ end.to_not raise_error
357
+ end
358
+ end
372
359
 
373
- it 'raise ImmatureSignature even when nbf claim is a string' do
374
- immature_payload = @payload.clone
375
- immature_payload['nbf'] = (Time.now.to_i).to_s
376
- secret = 'secret'
377
- jwt = JWT.encode(immature_payload, secret)
378
- expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ImmatureSignature)
379
- end
360
+ context 'jwt id claim' do
361
+ let :jti do
362
+ new_payload = payload.merge('iat' => Time.now.to_i)
363
+ key = data[:secret]
364
+ new_payload.merge('jti' => Digest::MD5.hexdigest("#{key}:#{new_payload['iat']}"))
365
+ end
366
+
367
+ let(:token) { JWT.encode jti, data[:secret] }
368
+
369
+ let :invalid_token do
370
+ jti.delete('iat')
371
+ JWT.encode jti, data[:secret]
372
+ end
380
373
 
381
- it 'performs normal decode with skipped not before check' do
382
- immature_payload = @payload.clone
383
- immature_payload['nbf'] = Time.now.to_i + 2
384
- secret = 'secret'
385
- jwt = JWT.encode(immature_payload, secret)
386
- decoded_payload = JWT.decode(jwt, secret, true, :verify_not_before => false)
387
- expect(decoded_payload).to include(immature_payload)
374
+ it 'invalid jti should raise JWT::InvalidJtiError' do
375
+ expect do
376
+ JWT.decode invalid_token, data[:secret], true, :verify_jti => true, 'jti' => jti['jti']
377
+ end.to raise_error JWT::InvalidJtiError
378
+ end
379
+
380
+ it 'valid jti should not raise JWT::InvalidJtiError' do
381
+ expect do
382
+ JWT.decode token, data[:secret], true, verify_jti: true, jti: jti['jti']
383
+ end.to_not raise_error
384
+ end
385
+ end
388
386
  end
389
387
 
390
- it 'performs normal decode using leeway' do
391
- immature_payload = @payload.clone
392
- immature_payload['nbf'] = Time.now.to_i - 2
393
- secret = 'secret'
394
- jwt = JWT.encode(immature_payload, secret)
395
- decoded_payload = JWT.decode(jwt, secret, true, :leeway => 3)
396
- expect(decoded_payload).to include(immature_payload)
388
+ context 'Base64' do
389
+ it 'urlsafe replace + / with - _' do
390
+ allow(Base64).to receive(:encode64) { 'string+with/non+url-safe/characters_' }
391
+ expect(JWT.base64url_encode('foo')).to eq('string-with_non-url-safe_characters_')
392
+ end
397
393
  end
398
394
 
399
395
  describe 'secure comparison' do
400
396
  it 'returns true if strings are equal' do
401
- expect(JWT.secure_compare('Foo', 'Foo')).to be true
397
+ expect(JWT.secure_compare('Foo', 'Foo')).to eq true
402
398
  end
403
399
 
404
400
  it 'returns false if either input is nil or empty' do
405
401
  [nil, ''].each do |bad|
406
- expect(JWT.secure_compare(bad, 'Foo')).to be false
407
- expect(JWT.secure_compare('Foo', bad)).to be false
402
+ expect(JWT.secure_compare(bad, 'Foo')).to eq false
403
+ expect(JWT.secure_compare('Foo', bad)).to eq false
408
404
  end
409
405
  end
410
406
 
411
407
  it 'retuns false if the strings are different' do
412
- expect(JWT.secure_compare('Foo', 'Bar')).to be false
408
+ expect(JWT.secure_compare('Foo', 'Bar')).to eq false
413
409
  end
414
410
  end
415
411
 
416
- # no method should leave OpenSSL.errors populated
417
- after do
418
- expect(OpenSSL.errors).to be_empty
419
- end
420
-
421
- it 'raise exception on invalid signature' do
422
- pubkey = OpenSSL::PKey::RSA.new(<<-PUBKEY)
423
- -----BEGIN PUBLIC KEY-----
424
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCaY7425h964bjaoLeUm
425
- SlZ8sK7VtVk9zHbGmZh2ygGYwfuUf2bmMye2Ofv99yDE/rd4loVIAcu7RVvDRgHq
426
- 3/CZTnIrSvHsiJQsHBNa3d+F1ihPfzURzf1M5k7CFReBj2SBXhDXd57oRfBQj12w
427
- CVhhwP6kGTAWuoppbIIIBfNF2lE/Nvm7lVVYQqL9xOrP/AQ4xRbpQlB8Ll9sO9Or
428
- SvbWhCDa/LMOWxHdmrcJi6XoSg1vnOyCoKbyAoauTt/XqdkHbkDdQ6HFbJieu9il
429
- LDZZNliPhfENuKeC2MCGVXTEu8Cqhy1w6e4axavLlXoYf4laJIZ/e7au8SqDbY0B
430
- xwIDAQAB
431
- -----END PUBLIC KEY-----
432
- PUBKEY
433
- jwt = (
434
- 'eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiY' \
435
- 'XVkIjoiMTA2MDM1Nzg5MTY4OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSI' \
436
- 'sImNpZCI6IjEwNjAzNTc4OTE2ODguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb' \
437
- '20iLCJpZCI6IjExNjQ1MjgyNDMwOTg1Njc4MjE2MyIsInRva2VuX2hhc2giOiJ' \
438
- '0Z2hEOUo4bjhWME4ydmN3NmVNaWpnIiwiaWF0IjoxMzIwNjcwOTc4LCJleHAiO' \
439
- 'jEzMjA2NzQ4Nzh9.D8x_wirkxDElqKdJBcsIws3Ogesk38okz6MN7zqC7nEAA7' \
440
- 'wcy1PxsROY1fmBvXSer0IQesAqOW-rPOCNReSn-eY8d53ph1x2HAF-AzEi3GOl' \
441
- '6hFycH8wj7Su6JqqyEbIVLxE7q7DkAZGaMPkxbTHs1EhSd5_oaKQ6O4xO3ZnnT4'
442
- )
443
- expect { JWT.decode(jwt, pubkey, true) }.to raise_error(JWT::DecodeError)
444
- end
445
-
446
- describe 'urlsafe base64 encoding' do
447
- it 'replaces + and / with - and _' do
448
- allow(Base64).to receive(:encode64) { 'string+with/non+url-safe/characters_' }
449
- expect(JWT.base64url_encode('foo')).to eq('string-with_non-url-safe_characters_')
450
- end
451
- end
452
-
453
- describe 'decoded_segments' do
454
- it 'allows access to the decoded header and payload' do
455
- secret = 'secret'
456
- jwt = JWT.encode(@payload, secret)
457
- decoded_segments = JWT.decoded_segments(jwt)
458
- expect(decoded_segments.size).to eq(4)
459
- expect(decoded_segments[0]).to eq('typ' => 'JWT', 'alg' => 'HS256')
460
- expect(decoded_segments[1]).to eq(@payload)
461
- end
462
- end
463
412
  end