jwt 1.5.1 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
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