jwt 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +1 -1
- data/jwt.gemspec +3 -3
- data/lib/jwt.rb +34 -2
- data/spec/helper.rb +0 -3
- data/spec/jwt_spec.rb +160 -66
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 22e5c1eafdf6a7e80397bbca142c3bff69524b21
|
4
|
+
data.tar.gz: e7acda6c7609412c393a5ebb29e575cb82618f9f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f897824dd5f6ccc196f16c9944914c14ca0d476d9bc030bc8872ad7e2c5234a3db907ee35b780715689549fe397a4e76521b0e3d06d37f2b06d890cbcf855633
|
7
|
+
data.tar.gz: aec0e48a68cee3b5694d97f47aa0b3721ac2eee8a82b038c172c884d3ccfc1ade1728a4d4bf92d7cedd2d8bca9273cb180a14fb4ee306b01e981b2e752341515
|
data/Rakefile
CHANGED
data/jwt.gemspec
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: jwt 1.
|
2
|
+
# stub: jwt 1.4.0 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "jwt"
|
6
|
-
s.version = "1.
|
6
|
+
s.version = "1.4.0"
|
7
7
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
9
9
|
s.require_paths = ["lib"]
|
10
10
|
s.authors = ["Jeff Lindsay"]
|
11
|
-
s.date = "2015-
|
11
|
+
s.date = "2015-03-10"
|
12
12
|
s.description = "JSON Web Token implementation in Ruby"
|
13
13
|
s.email = "progrium@gmail.com"
|
14
14
|
s.extra_rdoc_files = ["lib/jwt.rb", "lib/jwt/json.rb"]
|
data/lib/jwt.rb
CHANGED
@@ -11,8 +11,13 @@ require "jwt/json"
|
|
11
11
|
module JWT
|
12
12
|
class DecodeError < StandardError; end
|
13
13
|
class VerificationError < DecodeError; end
|
14
|
-
class ExpiredSignature <
|
15
|
-
class ImmatureSignature <
|
14
|
+
class ExpiredSignature < DecodeError; end
|
15
|
+
class ImmatureSignature < DecodeError; end
|
16
|
+
class InvalidIssuerError < DecodeError; end
|
17
|
+
class InvalidIatError < DecodeError; end
|
18
|
+
class InvalidAudError < DecodeError; end
|
19
|
+
class InvalidSubError < DecodeError; end
|
20
|
+
class InvalidJtiError < DecodeError; end
|
16
21
|
extend JWT::Json
|
17
22
|
|
18
23
|
module_function
|
@@ -105,8 +110,14 @@ module JWT
|
|
105
110
|
default_options = {
|
106
111
|
:verify_expiration => true,
|
107
112
|
:verify_not_before => true,
|
113
|
+
:verify_iss => true,
|
114
|
+
:verify_iat => true,
|
115
|
+
:verify_jti => true,
|
116
|
+
:verify_aud => true,
|
117
|
+
:verify_sub => true,
|
108
118
|
:leeway => 0
|
109
119
|
}
|
120
|
+
|
110
121
|
options = default_options.merge(options)
|
111
122
|
|
112
123
|
if verify
|
@@ -120,6 +131,27 @@ module JWT
|
|
120
131
|
if options[:verify_not_before] && payload.include?('nbf')
|
121
132
|
raise JWT::ImmatureSignature.new("Signature nbf has not been reached") unless payload['nbf'].to_i < (Time.now.to_i + options[:leeway])
|
122
133
|
end
|
134
|
+
if options[:verify_iss] && payload.include?('iss')
|
135
|
+
raise JWT::InvalidIssuerError.new("Invalid issuer") unless payload['iss'].to_s == options['iss'].to_s
|
136
|
+
end
|
137
|
+
if options[:verify_iat] && payload.include?('iat')
|
138
|
+
raise JWT::InvalidIatError.new("Invalid iat") unless (payload['iat'].is_a?(Integer) and payload['iat'].to_i <= Time.now.to_i)
|
139
|
+
end
|
140
|
+
if options[:verify_aud] && payload.include?('aud')
|
141
|
+
if payload['aud'].is_a?(Array)
|
142
|
+
raise JWT::InvalidAudError.new("Invalid audience") unless payload['aud'].include?(options['aud'])
|
143
|
+
else
|
144
|
+
raise JWT::InvalidAudError.new("Invalid audience") unless payload['aud'].to_s == options['aud'].to_s
|
145
|
+
end
|
146
|
+
end
|
147
|
+
if options[:verify_sub] && payload.include?('sub')
|
148
|
+
raise JWT::InvalidSubError.new("Invalid subject") unless payload['sub'].to_s == options['sub'].to_s
|
149
|
+
end
|
150
|
+
if options[:verify_jti] && payload.include?('jti')
|
151
|
+
raise JWT::InvalidJtiError.new("need iat for verify jwt id") unless payload.include?('iat')
|
152
|
+
raise JWT::InvalidJtiError.new("Not a uniq jwt id") unless options['jti'].to_s == Digest::MD5.hexdigest("#{key}:#{payload['iat']}")
|
153
|
+
end
|
154
|
+
|
123
155
|
return payload,header
|
124
156
|
end
|
125
157
|
|
data/spec/helper.rb
CHANGED
data/spec/jwt_spec.rb
CHANGED
@@ -2,81 +2,175 @@ require 'helper'
|
|
2
2
|
|
3
3
|
describe JWT do
|
4
4
|
before do
|
5
|
-
@payload = {
|
5
|
+
@payload = {'foo' => 'bar', 'exp' => Time.now.to_i + 1, 'nbf' => Time.now.to_i - 1 }
|
6
6
|
end
|
7
7
|
|
8
|
-
it
|
9
|
-
secret =
|
8
|
+
it 'encodes and decodes JWTs' do
|
9
|
+
secret = 'secret'
|
10
10
|
jwt = JWT.encode(@payload, secret)
|
11
11
|
decoded_payload = JWT.decode(jwt, secret)
|
12
12
|
expect(decoded_payload).to include(@payload)
|
13
13
|
end
|
14
14
|
|
15
|
-
it
|
15
|
+
it 'encodes and decodes JWTs for RSA signatures' do
|
16
16
|
private_key = OpenSSL::PKey::RSA.generate(512)
|
17
|
-
jwt = JWT.encode(@payload, private_key,
|
17
|
+
jwt = JWT.encode(@payload, private_key, 'RS256')
|
18
18
|
decoded_payload = JWT.decode(jwt, private_key.public_key)
|
19
19
|
expect(decoded_payload).to include(@payload)
|
20
20
|
end
|
21
21
|
|
22
|
-
it
|
22
|
+
it 'encodes and decodes JWTs with custom header fields' do
|
23
23
|
private_key = OpenSSL::PKey::RSA.generate(512)
|
24
|
-
jwt = JWT.encode(@payload, private_key,
|
24
|
+
jwt = JWT.encode(@payload, private_key, 'RS256', {'kid' => 'default'})
|
25
25
|
decoded_payload = JWT.decode(jwt) do |header|
|
26
|
-
expect(header[
|
26
|
+
expect(header['kid']).to eq('default')
|
27
27
|
private_key.public_key
|
28
28
|
end
|
29
29
|
expect(decoded_payload).to include(@payload)
|
30
30
|
end
|
31
31
|
|
32
|
-
it
|
33
|
-
example_payload = {
|
32
|
+
it 'decodes valid JWTs' do
|
33
|
+
example_payload = {'hello' => 'world'}
|
34
34
|
example_secret = 'secret'
|
35
35
|
example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
|
36
36
|
decoded_payload = JWT.decode(example_jwt, example_secret)
|
37
37
|
expect(decoded_payload).to include(example_payload)
|
38
38
|
end
|
39
39
|
|
40
|
-
it
|
40
|
+
it 'decodes valid JWTs with iss' do
|
41
|
+
example_payload = {'hello' => 'world', 'iss' => 'jwtiss'}
|
42
|
+
example_secret = 'secret'
|
43
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaXNzIjoiand0aXNzIn0.nTZkyYfpGUyKULaj45lXw_1gXXjHvGW4h5V7okHdUqQ'
|
44
|
+
decoded_payload = JWT.decode(example_jwt, example_secret, true, {'iss' => 'jwtiss'})
|
45
|
+
expect(decoded_payload).to include(example_payload)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'raises invalid issuer' do
|
49
|
+
# example_payload = {'hello' => 'world', 'iss' => 'jwtiss'}
|
50
|
+
example_payload2 = {'hello' => 'world'}
|
51
|
+
|
52
|
+
example_secret = 'secret'
|
53
|
+
|
54
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaXNzIjoiand0aXNzIn0.nTZkyYfpGUyKULaj45lXw_1gXXjHvGW4h5V7okHdUqQ'
|
55
|
+
expect{ JWT.decode(example_jwt, example_secret, true, {'iss' => 'jwt_iss'}) }.to raise_error(JWT::InvalidIssuerError)
|
56
|
+
|
57
|
+
example_jwt2 = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
|
58
|
+
decode_payload2 = JWT.decode(example_jwt2, example_secret, true, {'iss' => 'jwt_iss'})
|
59
|
+
expect(decode_payload2).to include(example_payload2)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'decodes valid JWTs with iat' do
|
63
|
+
example_payload = {'hello' => 'world', 'iat' => 1425917209}
|
64
|
+
example_secret = 'secret'
|
65
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5fQ.m4F-Ugo7aLnLunBBO3BeDidyWMx8T9eoJz6FW2rgQhU'
|
66
|
+
decoded_payload = JWT.decode(example_jwt, example_secret, true, {'iat' => true})
|
67
|
+
expect(decoded_payload).to include(example_payload)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'raises decode exception when iat is invalid' do
|
71
|
+
# example_payload = {'hello' => 'world', 'iat' => 'abc'}
|
72
|
+
example_secret = 'secret'
|
73
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoiMTQyNTkxNzIwOSJ9.Mn_vk61xWjIhbXFqAB0nFmNkDiCmfzUgl_LaCKRT6S8'
|
74
|
+
expect{ JWT.decode(example_jwt, example_secret, true, {'iat' => 1425917209}) }.to raise_error(JWT::InvalidIatError)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'decodes valid JWTs with jti' do
|
78
|
+
example_payload = {'hello' => 'world', 'iat' => 1425917209, 'jti' => Digest::MD5.hexdigest('secret:1425917209')}
|
79
|
+
example_secret = 'secret'
|
80
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5LCJqdGkiOiI1NWM3NzZlMjFmN2NiZDg3OWMwNmZhYzAxOGRhYzQwMiJ9.ET0hb-VTUOL3M22oG13ofzvGPLMAncbF8rdNDIqo8tg'
|
81
|
+
decoded_payload = JWT.decode(example_jwt, example_secret, true, {'jti' => Digest::MD5.hexdigest('secret:1425917209')})
|
82
|
+
expect(decoded_payload).to include(example_payload)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'raises decode exception when jti is invalid' do
|
86
|
+
# example_payload = {'hello' => 'world', 'iat' => 1425917209, 'jti' => Digest::MD5.hexdigest('secret:1425917209')}
|
87
|
+
example_secret = 'secret'
|
88
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5LCJqdGkiOiI1NWM3NzZlMjFmN2NiZDg3OWMwNmZhYzAxOGRhYzQwMiJ9.ET0hb-VTUOL3M22oG13ofzvGPLMAncbF8rdNDIqo8tg'
|
89
|
+
expect{ JWT.decode(example_jwt, example_secret, true, {'jti' => Digest::MD5.hexdigest('secret:1425922032')}) }.to raise_error(JWT::InvalidJtiError)
|
90
|
+
expect{ JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::InvalidJtiError)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'raises decode exception when jti without iat' do
|
94
|
+
# example_payload = {'hello' => 'world', 'jti' => Digest::MD5.hexdigest('secret:1425917209')}
|
95
|
+
example_secret = 'secret'
|
96
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwianRpIjoiNTVjNzc2ZTIxZjdjYmQ4NzljMDZmYWMwMThkYWM0MDIifQ.n0foJCnCM_-_xUvG_TOmR9mYpL2y0UqZOD_gv33djeE'
|
97
|
+
expect{ JWT.decode(example_jwt, example_secret, true, {'jti' => Digest::MD5.hexdigest('secret:1425922032')}) }.to raise_error(JWT::InvalidJtiError)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'decodes valid JWTs with aud' do
|
101
|
+
example_payload = {'hello' => 'world', 'aud' => 'url:pnd'}
|
102
|
+
example_payload2 = {'hello' => 'world', 'aud' => ['url:pnd', 'aud:yes']}
|
103
|
+
example_secret = 'secret'
|
104
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiYXVkIjoidXJsOnBuZCJ9._gT5veUtNiZD7wLEC6Gd0-nkQV3cl1z8G0zXq8qcd-8'
|
105
|
+
example_jwt2 = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiYXVkIjpbInVybDpwbmQiLCJhdWQ6eWVzIl19.qNPNcT4X9B5uI91rIwbW2bIPTsp8wbRYW3jkZkrmqbQ'
|
106
|
+
decoded_payload = JWT.decode(example_jwt, example_secret, true, {'aud' => 'url:pnd'})
|
107
|
+
decoded_payload2 = JWT.decode(example_jwt2, example_secret, true, {'aud' => 'url:pnd'})
|
108
|
+
expect(decoded_payload).to include(example_payload)
|
109
|
+
expect(decoded_payload2).to include(example_payload2)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'raises deode exception when aud is invalid' do
|
113
|
+
# example_payload = {'hello' => 'world', 'aud' => 'url:pnd'}
|
114
|
+
example_secret = 'secret'
|
115
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiYXVkIjoidXJsOnBuZCJ9._gT5veUtNiZD7wLEC6Gd0-nkQV3cl1z8G0zXq8qcd-8'
|
116
|
+
expect{ JWT.decode(example_jwt, example_secret, true, {'aud' => 'wrong:aud'}) }.to raise_error(JWT::InvalidAudError)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'decodes valid JWTs with sub' do
|
120
|
+
example_payload = {'hello' => 'world', 'sub' => 'subject'}
|
121
|
+
example_secret = 'secret'
|
122
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwic3ViIjoic3ViamVjdCJ9.QUnNVZm4SPB4vP2zY9m1LoUSOx-5oGXBhj7R89D_UtA'
|
123
|
+
decoded_payload = JWT.decode(example_jwt, example_secret, true, {'sub' => 'subject'})
|
124
|
+
expect(decoded_payload).to include(example_payload)
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'raise decode exception when the sub is invalid' do
|
128
|
+
# example_payload = {'hello' => 'world', 'sub' => 'subject'}
|
129
|
+
example_secret = 'secret'
|
130
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwic3ViIjoic3ViamVjdCJ9.QUnNVZm4SPB4vP2zY9m1LoUSOx-5oGXBhj7R89D_UtA'
|
131
|
+
expect{ JWT.decode(example_jwt, example_secret, true, {'iss' => 'subject'}) }.to raise_error(JWT::InvalidSubError)
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'raises decode exception when the token is invalid' do
|
41
135
|
example_secret = 'secret'
|
42
136
|
# Same as above exmaple with some random bytes replaced
|
43
137
|
example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHiMomlwIjogIkJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
|
44
138
|
expect { JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::DecodeError)
|
45
139
|
end
|
46
140
|
|
47
|
-
it
|
141
|
+
it 'raises verification exception with wrong hmac key' do
|
48
142
|
right_secret = 'foo'
|
49
143
|
bad_secret = 'bar'
|
50
|
-
jwt_message = JWT.encode(@payload, right_secret,
|
144
|
+
jwt_message = JWT.encode(@payload, right_secret, 'HS256')
|
51
145
|
expect { JWT.decode(jwt_message, bad_secret) }.to raise_error(JWT::VerificationError)
|
52
146
|
end
|
53
147
|
|
54
|
-
it
|
148
|
+
it 'raises verification exception with wrong rsa key' do
|
55
149
|
right_private_key = OpenSSL::PKey::RSA.generate(512)
|
56
150
|
bad_private_key = OpenSSL::PKey::RSA.generate(512)
|
57
|
-
jwt = JWT.encode(@payload, right_private_key,
|
151
|
+
jwt = JWT.encode(@payload, right_private_key, 'RS256')
|
58
152
|
expect { JWT.decode(jwt, bad_private_key.public_key) }.to raise_error(JWT::VerificationError)
|
59
153
|
end
|
60
154
|
|
61
|
-
it
|
155
|
+
it 'raises decode exception with invalid signature' do
|
62
156
|
example_secret = 'secret'
|
63
157
|
example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.'
|
64
158
|
expect { JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::DecodeError)
|
65
159
|
end
|
66
160
|
|
67
|
-
it
|
68
|
-
expect { JWT.decode(
|
161
|
+
it 'raises decode exception with nonexistent header' do
|
162
|
+
expect { JWT.decode('..stuff') }.to raise_error(JWT::DecodeError)
|
69
163
|
end
|
70
164
|
|
71
|
-
it
|
72
|
-
expect { JWT.decode(
|
165
|
+
it 'raises decode exception with nonexistent payload' do
|
166
|
+
expect { JWT.decode('eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9..stuff') }.to raise_error(JWT::DecodeError)
|
73
167
|
end
|
74
168
|
|
75
|
-
it
|
169
|
+
it 'raises decode exception with nil jwt' do
|
76
170
|
expect { JWT.decode(nil) }.to raise_error(JWT::DecodeError)
|
77
171
|
end
|
78
172
|
|
79
|
-
it
|
173
|
+
it 'allows decoding without key' do
|
80
174
|
right_secret = 'foo'
|
81
175
|
bad_secret = 'bar'
|
82
176
|
jwt = JWT.encode(@payload, right_secret)
|
@@ -84,35 +178,35 @@ describe JWT do
|
|
84
178
|
expect(decoded_payload).to include(@payload)
|
85
179
|
end
|
86
180
|
|
87
|
-
it
|
181
|
+
it 'checks the key when verify is truthy' do
|
88
182
|
right_secret = 'foo'
|
89
183
|
bad_secret = 'bar'
|
90
184
|
jwt = JWT.encode(@payload, right_secret)
|
91
|
-
verify =
|
185
|
+
verify = 'yes' =~ /^y/i
|
92
186
|
expect { JWT.decode(jwt, bad_secret, verify) }.to raise_error(JWT::DecodeError)
|
93
187
|
end
|
94
188
|
|
95
|
-
it
|
96
|
-
expect { JWT.encode(@payload,
|
189
|
+
it 'raises exception on unsupported crypto algorithm' do
|
190
|
+
expect { JWT.encode(@payload, 'secret', 'HS1024') }.to raise_error(NotImplementedError)
|
97
191
|
end
|
98
192
|
|
99
|
-
it
|
193
|
+
it 'encodes and decodes plaintext JWTs' do
|
100
194
|
jwt = JWT.encode(@payload, nil, nil)
|
101
195
|
expect(jwt.split('.').length).to eq(2)
|
102
196
|
decoded_payload = JWT.decode(jwt, nil, nil)
|
103
197
|
expect(decoded_payload).to include(@payload)
|
104
198
|
end
|
105
199
|
|
106
|
-
it
|
200
|
+
it 'requires a signature segment when verify is truthy' do
|
107
201
|
jwt = JWT.encode(@payload, nil, nil)
|
108
202
|
expect(jwt.split('.').length).to eq(2)
|
109
203
|
expect { JWT.decode(jwt, nil, true) }.to raise_error(JWT::DecodeError)
|
110
204
|
end
|
111
205
|
|
112
|
-
it
|
113
|
-
secret =
|
206
|
+
it 'does not use == to compare digests' do
|
207
|
+
secret = 'secret'
|
114
208
|
jwt = JWT.encode(@payload, secret)
|
115
|
-
crypto_segment = jwt.split(
|
209
|
+
crypto_segment = jwt.split('.').last
|
116
210
|
|
117
211
|
signature = JWT.base64url_decode(crypto_segment)
|
118
212
|
expect(signature).not_to receive('==')
|
@@ -122,96 +216,96 @@ describe JWT do
|
|
122
216
|
JWT.decode(jwt, secret)
|
123
217
|
end
|
124
218
|
|
125
|
-
it
|
219
|
+
it 'raises error when expired' do
|
126
220
|
expired_payload = @payload.clone
|
127
221
|
expired_payload['exp'] = Time.now.to_i - 1
|
128
|
-
secret =
|
222
|
+
secret = 'secret'
|
129
223
|
jwt = JWT.encode(expired_payload, secret)
|
130
224
|
expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ExpiredSignature)
|
131
225
|
end
|
132
226
|
|
133
|
-
it
|
227
|
+
it 'raise ExpiredSignature even when exp claims is a string' do
|
134
228
|
expired_payload = @payload.clone
|
135
229
|
expired_payload['exp'] = (Time.now.to_i).to_s
|
136
|
-
secret =
|
230
|
+
secret = 'secret'
|
137
231
|
jwt = JWT.encode(expired_payload, secret)
|
138
232
|
expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ExpiredSignature)
|
139
233
|
end
|
140
234
|
|
141
|
-
it
|
235
|
+
it 'performs normal decode with skipped expiration check' do
|
142
236
|
expired_payload = @payload.clone
|
143
237
|
expired_payload['exp'] = Time.now.to_i - 1
|
144
|
-
secret =
|
238
|
+
secret = 'secret'
|
145
239
|
jwt = JWT.encode(expired_payload, secret)
|
146
240
|
decoded_payload = JWT.decode(jwt, secret, true, {:verify_expiration => false})
|
147
241
|
expect(decoded_payload).to include(expired_payload)
|
148
242
|
end
|
149
243
|
|
150
|
-
it
|
244
|
+
it 'performs normal decode using leeway' do
|
151
245
|
expired_payload = @payload.clone
|
152
246
|
expired_payload['exp'] = Time.now.to_i - 2
|
153
|
-
secret =
|
247
|
+
secret = 'secret'
|
154
248
|
jwt = JWT.encode(expired_payload, secret)
|
155
249
|
decoded_payload = JWT.decode(jwt, secret, true, {:leeway => 3})
|
156
250
|
expect(decoded_payload).to include(expired_payload)
|
157
251
|
end
|
158
252
|
|
159
|
-
it
|
253
|
+
it 'raises error when before nbf' do
|
160
254
|
immature_payload = @payload.clone
|
161
255
|
immature_payload['nbf'] = Time.now.to_i + 1
|
162
|
-
secret =
|
256
|
+
secret = 'secret'
|
163
257
|
jwt = JWT.encode(immature_payload, secret)
|
164
258
|
expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ImmatureSignature)
|
165
259
|
end
|
166
260
|
|
167
|
-
it
|
261
|
+
it 'doesnt raise error when after nbf' do
|
168
262
|
mature_payload = @payload.clone
|
169
|
-
secret =
|
263
|
+
secret = 'secret'
|
170
264
|
jwt = JWT.encode(mature_payload, secret)
|
171
265
|
decoded_payload = JWT.decode(jwt, secret, true, {:verify_expiration => false})
|
172
266
|
expect(decoded_payload).to include(mature_payload)
|
173
267
|
end
|
174
268
|
|
175
|
-
it
|
269
|
+
it 'raise ImmatureSignature even when nbf claim is a string' do
|
176
270
|
immature_payload = @payload.clone
|
177
271
|
immature_payload['nbf'] = (Time.now.to_i).to_s
|
178
|
-
secret =
|
272
|
+
secret = 'secret'
|
179
273
|
jwt = JWT.encode(immature_payload, secret)
|
180
274
|
expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ImmatureSignature)
|
181
275
|
end
|
182
276
|
|
183
|
-
it
|
277
|
+
it 'performs normal decode with skipped not before check' do
|
184
278
|
immature_payload = @payload.clone
|
185
279
|
immature_payload['nbf'] = Time.now.to_i + 2
|
186
|
-
secret =
|
280
|
+
secret = 'secret'
|
187
281
|
jwt = JWT.encode(immature_payload, secret)
|
188
282
|
decoded_payload = JWT.decode(jwt, secret, true, {:verify_not_before => false})
|
189
283
|
expect(decoded_payload).to include(immature_payload)
|
190
284
|
end
|
191
285
|
|
192
|
-
it
|
286
|
+
it 'performs normal decode using leeway' do
|
193
287
|
immature_payload = @payload.clone
|
194
288
|
immature_payload['nbf'] = Time.now.to_i - 2
|
195
|
-
secret =
|
289
|
+
secret = 'secret'
|
196
290
|
jwt = JWT.encode(immature_payload, secret)
|
197
291
|
decoded_payload = JWT.decode(jwt, secret, true, {:leeway => 3})
|
198
292
|
expect(decoded_payload).to include(immature_payload)
|
199
293
|
end
|
200
294
|
|
201
|
-
describe
|
202
|
-
it
|
203
|
-
expect(JWT.secure_compare(
|
295
|
+
describe 'secure comparison' do
|
296
|
+
it 'returns true if strings are equal' do
|
297
|
+
expect(JWT.secure_compare('Foo', 'Foo')).to be true
|
204
298
|
end
|
205
299
|
|
206
|
-
it
|
207
|
-
[nil,
|
208
|
-
expect(JWT.secure_compare(bad,
|
209
|
-
expect(JWT.secure_compare(
|
300
|
+
it 'returns false if either input is nil or empty' do
|
301
|
+
[nil, ''].each do |bad|
|
302
|
+
expect(JWT.secure_compare(bad, 'Foo')).to be false
|
303
|
+
expect(JWT.secure_compare('Foo', bad)).to be false
|
210
304
|
end
|
211
305
|
end
|
212
306
|
|
213
|
-
it
|
214
|
-
expect(JWT.secure_compare(
|
307
|
+
it 'retuns false if the strings are different' do
|
308
|
+
expect(JWT.secure_compare('Foo', 'Bar')).to be false
|
215
309
|
end
|
216
310
|
end
|
217
311
|
|
@@ -220,7 +314,7 @@ describe JWT do
|
|
220
314
|
expect(OpenSSL.errors).to be_empty
|
221
315
|
end
|
222
316
|
|
223
|
-
it
|
317
|
+
it 'raise exception on invalid signature' do
|
224
318
|
pubkey = OpenSSL::PKey::RSA.new(<<-PUBKEY)
|
225
319
|
-----BEGIN PUBLIC KEY-----
|
226
320
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCaY7425h964bjaoLeUm
|
@@ -245,20 +339,20 @@ PUBKEY
|
|
245
339
|
expect { JWT.decode(jwt, pubkey, true) }.to raise_error(JWT::DecodeError)
|
246
340
|
end
|
247
341
|
|
248
|
-
describe
|
249
|
-
it
|
250
|
-
allow(Base64).to receive(:encode64) {
|
251
|
-
expect(JWT.base64url_encode(
|
342
|
+
describe 'urlsafe base64 encoding' do
|
343
|
+
it 'replaces + and / with - and _' do
|
344
|
+
allow(Base64).to receive(:encode64) { 'string+with/non+url-safe/characters_' }
|
345
|
+
expect(JWT.base64url_encode('foo')).to eq('string-with_non-url-safe_characters_')
|
252
346
|
end
|
253
347
|
end
|
254
348
|
|
255
349
|
describe 'decoded_segments' do
|
256
|
-
it
|
257
|
-
secret =
|
350
|
+
it 'allows access to the decoded header and payload' do
|
351
|
+
secret = 'secret'
|
258
352
|
jwt = JWT.encode(@payload, secret)
|
259
353
|
decoded_segments = JWT.decoded_segments(jwt)
|
260
354
|
expect(decoded_segments.size).to eq(4)
|
261
|
-
expect(decoded_segments[0]).to eq({
|
355
|
+
expect(decoded_segments[0]).to eq({'typ' => 'JWT', 'alg' => 'HS256'})
|
262
356
|
expect(decoded_segments[1]).to eq(@payload)
|
263
357
|
end
|
264
358
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jwt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeff Lindsay
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-03-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: echoe
|