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.
- checksums.yaml +4 -4
- data/LICENSE +7 -0
- data/Manifest +3 -1
- data/README.md +368 -0
- data/Rakefile +6 -6
- data/jwt.gemspec +6 -6
- data/lib/jwt.rb +45 -45
- data/lib/jwt/json.rb +2 -2
- data/spec/jwt_spec.rb +327 -378
- data/spec/{helper.rb → spec_helper.rb} +14 -1
- metadata +8 -4
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
|
-
#
|
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
|
33
|
+
if %w(HS256 HS384 HS512).include?(algorithm)
|
34
34
|
sign_hmac(algorithm, msg, key)
|
35
|
-
elsif
|
35
|
+
elsif %w(RS256 RS384 RS512).include?(algorithm)
|
36
36
|
sign_rsa(algorithm, msg, key)
|
37
|
-
elsif
|
37
|
+
elsif %w(ES256 ES384 ES512).include?(algorithm)
|
38
38
|
sign_ecdsa(algorithm, msg, key)
|
39
39
|
else
|
40
|
-
fail NotImplementedError
|
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
|
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
|
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
|
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
|
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
|
137
|
+
fail(JWT::DecodeError, 'Not enough or too many segments') unless header && payload
|
138
138
|
|
139
139
|
default_options = {
|
140
|
-
:
|
141
|
-
:
|
142
|
-
:
|
143
|
-
:
|
144
|
-
:
|
145
|
-
:
|
146
|
-
:
|
147
|
-
:
|
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
|
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
|
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
|
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[
|
167
|
-
fail
|
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
|
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[
|
173
|
-
if payload[
|
174
|
-
fail
|
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
|
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] &&
|
180
|
-
fail
|
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
|
184
|
-
fail
|
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
|
197
|
-
fail
|
198
|
-
elsif
|
199
|
-
fail
|
200
|
-
elsif
|
201
|
-
fail
|
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
|
203
|
+
fail JWT::VerificationError, 'Algorithm not supported'
|
204
204
|
end
|
205
205
|
rescue OpenSSL::PKey::PKeyError
|
206
|
-
raise JWT::VerificationError
|
206
|
+
raise JWT::VerificationError, 'Signature verification raised'
|
207
207
|
ensure
|
208
208
|
OpenSSL.errors.clear
|
209
209
|
end
|
data/lib/jwt/json.rb
CHANGED
@@ -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
|
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
|
24
|
+
raise JWT::DecodeError, 'Invalid segment encoding'
|
25
25
|
end
|
26
26
|
|
27
27
|
def encode_json(raw)
|
data/spec/jwt_spec.rb
CHANGED
@@ -1,463 +1,412 @@
|
|
1
|
-
|
2
|
-
require '
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'jwt'
|
3
3
|
|
4
4
|
describe JWT do
|
5
|
-
|
6
|
-
|
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
|
-
|
10
|
-
|
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
|
-
|
17
|
-
|
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
|
-
|
24
|
-
|
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
|
-
|
34
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
72
|
-
|
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
|
-
|
80
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
97
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
101
|
+
expect do
|
102
|
+
JWT.decode data[alg], key
|
103
|
+
end.to raise_error JWT::DecodeError
|
104
|
+
end
|
103
105
|
|
104
|
-
|
105
|
-
|
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
|
-
|
108
|
-
|
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
|
-
|
112
|
-
|
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
|
-
|
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
|
-
|
119
|
-
|
124
|
+
it 'should generate a valid token' do
|
125
|
+
jwt_payload, header = JWT.decode data[alg], data["#{alg}_public"]
|
120
126
|
|
121
|
-
|
122
|
-
|
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
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
142
|
-
|
143
|
-
|
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
|
-
|
150
|
-
|
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
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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 '
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
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
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
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
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
-
|
193
|
-
|
194
|
-
|
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
|
-
|
218
|
+
let :token do
|
219
|
+
payload.merge!(nbf: nbf)
|
201
220
|
|
202
|
-
|
203
|
-
|
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
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
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
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
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
|
-
|
232
|
-
|
233
|
-
|
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
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
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
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
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
|
-
|
256
|
-
|
257
|
-
|
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
|
-
|
260
|
-
|
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
|
-
|
264
|
-
|
265
|
-
|
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
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
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
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
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
|
-
|
284
|
-
|
285
|
-
|
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
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
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
|
-
|
293
|
-
|
294
|
-
|
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
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
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
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
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
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
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
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
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
|
-
|
321
|
-
|
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
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
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
|
-
|
332
|
-
|
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
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
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
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
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
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
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
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
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
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
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
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
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
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
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
|
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
|
407
|
-
expect(JWT.secure_compare('Foo', bad)).to
|
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
|
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
|