jwt 1.5.0 → 1.5.1
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/Rakefile +2 -1
- data/jwt.gemspec +3 -3
- data/lib/jwt.rb +53 -46
- data/lib/jwt/json.rb +2 -0
- data/spec/helper.rb +17 -0
- data/spec/jwt_spec.rb +89 -60
- 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: 20bef3dbabb4260b05cd6119dc1b552d630bd3e1
|
4
|
+
data.tar.gz: bb02d4a2bb19011a8eb531020d4780f31d44af7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8a0290f1b20390efb50c613da54a37072ebceee5dc11a9cec82cf67fa9e4002f3974bb35bd503b4dd7fdaacaed47c46f6f611aa8a66200cf44fbe40eaae8a2c
|
7
|
+
data.tar.gz: c9bb1d911b9d3cbabe362d69c6a556c8596f1dcd2485635875d02a74e93768b56fae8c5ee683ba03c5223b8696acda8080bbe408ea580ace5052ff48dfb7cb7b
|
data/Rakefile
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
require 'rubygems'
|
2
3
|
require 'rake'
|
3
4
|
require 'echoe'
|
4
5
|
|
5
|
-
Echoe.new('jwt', '1.5.
|
6
|
+
Echoe.new('jwt', '1.5.1') do |p|
|
6
7
|
p.description = 'JSON Web Token implementation in Ruby'
|
7
8
|
p.url = 'http://github.com/progrium/ruby-jwt'
|
8
9
|
p.author = 'Jeff Lindsay'
|
data/jwt.gemspec
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: jwt 1.5.
|
2
|
+
# stub: jwt 1.5.1 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "jwt"
|
6
|
-
s.version = "1.5.
|
6
|
+
s.version = "1.5.1"
|
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-06-22"
|
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
@@ -1,13 +1,13 @@
|
|
1
|
-
#
|
2
|
-
# JSON Web Token implementation
|
3
|
-
#
|
4
|
-
# Should be up to date with the latest spec:
|
5
|
-
# http://self-issued.info/docs/draft-jones-json-web-token-06.html
|
1
|
+
# encoding: utf-8
|
6
2
|
|
7
3
|
require 'base64'
|
8
4
|
require 'openssl'
|
9
5
|
require 'jwt/json'
|
10
6
|
|
7
|
+
# JSON Web Token implementation
|
8
|
+
#
|
9
|
+
# Should be up to date with the latest spec:
|
10
|
+
# http://self-issued.info/docs/draft-jones-json-web-token-06.html
|
11
11
|
module JWT
|
12
12
|
class DecodeError < StandardError; end
|
13
13
|
class VerificationError < DecodeError; end
|
@@ -24,7 +24,7 @@ module JWT
|
|
24
24
|
NAMED_CURVES = {
|
25
25
|
'prime256v1' => 'ES256',
|
26
26
|
'secp384r1' => 'ES384',
|
27
|
-
'secp521r1' => 'ES512'
|
27
|
+
'secp521r1' => 'ES512'
|
28
28
|
}
|
29
29
|
|
30
30
|
module_function
|
@@ -37,7 +37,7 @@ module JWT
|
|
37
37
|
elsif ['ES256', 'ES384', 'ES512'].include?(algorithm)
|
38
38
|
sign_ecdsa(algorithm, msg, key)
|
39
39
|
else
|
40
|
-
|
40
|
+
fail NotImplementedError.new('Unsupported signing method')
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
@@ -48,11 +48,11 @@ 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
|
-
|
51
|
+
fail IncorrectAlgorithm.new("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'))
|
55
|
-
private_key.dsa_sign_asn1(digest.digest(msg))
|
55
|
+
asn1_to_raw(private_key.dsa_sign_asn1(digest.digest(msg)), private_key)
|
56
56
|
end
|
57
57
|
|
58
58
|
def verify_rsa(algorithm, public_key, signing_input, signature)
|
@@ -62,11 +62,11 @@ 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
|
-
|
65
|
+
fail IncorrectAlgorithm.new("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'))
|
69
|
-
public_key.dsa_verify_asn1(digest.digest(signing_input), signature)
|
69
|
+
public_key.dsa_verify_asn1(digest.digest(signing_input), raw_to_asn1(signature, public_key))
|
70
70
|
end
|
71
71
|
|
72
72
|
def sign_hmac(algorithm, msg, key)
|
@@ -83,7 +83,7 @@ module JWT
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def encoded_header(algorithm='HS256', header_fields={})
|
86
|
-
header = {'typ' => 'JWT', 'alg' => algorithm}.merge(header_fields)
|
86
|
+
header = { 'typ' => 'JWT', 'alg' => algorithm }.merge(header_fields)
|
87
87
|
base64url_encode(encode_json(header))
|
88
88
|
end
|
89
89
|
|
@@ -111,8 +111,8 @@ module JWT
|
|
111
111
|
|
112
112
|
def raw_segments(jwt, verify=true)
|
113
113
|
segments = jwt.split('.')
|
114
|
-
required_num_segments = verify ? [3] : [2,3]
|
115
|
-
|
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
|
116
116
|
segments
|
117
117
|
end
|
118
118
|
|
@@ -131,10 +131,10 @@ module JWT
|
|
131
131
|
end
|
132
132
|
|
133
133
|
def decode(jwt, key=nil, verify=true, options={}, &keyfinder)
|
134
|
-
|
134
|
+
fail JWT::DecodeError.new('Nil JSON web token') unless jwt
|
135
135
|
|
136
136
|
header, payload, signature, signing_input = decoded_segments(jwt, verify)
|
137
|
-
|
137
|
+
fail JWT::DecodeError.new('Not enough or too many segments') unless header && payload
|
138
138
|
|
139
139
|
default_options = {
|
140
140
|
:verify_expiration => true,
|
@@ -152,64 +152,60 @@ 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
|
-
|
155
|
+
fail JWT::IncorrectAlgorithm.new('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
|
-
|
161
|
+
fail JWT::ExpiredSignature.new('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
|
-
|
164
|
+
fail JWT::ImmatureSignature.new('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] &&
|
167
|
-
|
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
|
168
168
|
end
|
169
169
|
if options[:verify_iat] && payload.include?('iat')
|
170
|
-
|
170
|
+
fail JWT::InvalidIatError.new('Invalid iat') unless payload['iat'].is_a?(Integer) && payload['iat'].to_i <= Time.now.to_i
|
171
171
|
end
|
172
|
-
if options[:verify_aud] &&
|
172
|
+
if options[:verify_aud] && options['aud']
|
173
173
|
if payload['aud'].is_a?(Array)
|
174
|
-
|
174
|
+
fail JWT::InvalidAudError.new('Invalid audience') unless payload['aud'].include?(options['aud'].to_s)
|
175
175
|
else
|
176
|
-
|
176
|
+
fail JWT::InvalidAudError.new("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
179
|
if options[:verify_sub] && payload.include?('sub')
|
180
|
-
|
180
|
+
fail JWT::InvalidSubError.new("Invalid subject. Expected #{options['sub']}, received #{payload['sub']}") unless payload['sub'].to_s == options['sub'].to_s
|
181
181
|
end
|
182
182
|
if options[:verify_jti] && payload.include?('jti')
|
183
|
-
|
184
|
-
|
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']}")
|
185
185
|
end
|
186
186
|
|
187
|
-
|
187
|
+
[payload, header]
|
188
188
|
end
|
189
189
|
|
190
190
|
def signature_algorithm_and_key(header, key, &keyfinder)
|
191
|
-
if keyfinder
|
192
|
-
key = keyfinder.call(header)
|
193
|
-
end
|
191
|
+
key = keyfinder.call(header) if keyfinder
|
194
192
|
[header['alg'], key]
|
195
193
|
end
|
196
194
|
|
197
195
|
def verify_signature(algo, key, signing_input, signature)
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
raise JWT::VerificationError.new('Algorithm not supported')
|
207
|
-
end
|
208
|
-
rescue OpenSSL::PKey::PKeyError
|
209
|
-
raise JWT::VerificationError.new('Signature verification failed')
|
210
|
-
ensure
|
211
|
-
OpenSSL.errors.clear
|
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)
|
202
|
+
else
|
203
|
+
fail JWT::VerificationError.new('Algorithm not supported')
|
212
204
|
end
|
205
|
+
rescue OpenSSL::PKey::PKeyError
|
206
|
+
raise JWT::VerificationError.new('Signature verification failed')
|
207
|
+
ensure
|
208
|
+
OpenSSL.errors.clear
|
213
209
|
end
|
214
210
|
|
215
211
|
# From devise
|
@@ -223,4 +219,15 @@ module JWT
|
|
223
219
|
res == 0
|
224
220
|
end
|
225
221
|
|
222
|
+
def raw_to_asn1(signature, private_key)
|
223
|
+
byte_size = (private_key.group.degree + 7) / 8
|
224
|
+
r = signature[0..(byte_size - 1)]
|
225
|
+
s = signature[byte_size..-1]
|
226
|
+
OpenSSL::ASN1::Sequence.new([r, s].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
|
227
|
+
end
|
228
|
+
|
229
|
+
def asn1_to_raw(signature, public_key)
|
230
|
+
byte_size = (public_key.group.degree + 7) / 8
|
231
|
+
OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
|
232
|
+
end
|
226
233
|
end
|
data/lib/jwt/json.rb
CHANGED
data/spec/helper.rb
CHANGED
@@ -1,2 +1,19 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
require 'rspec'
|
3
|
+
require 'simplecov'
|
4
|
+
require 'simplecov-json'
|
5
|
+
|
6
|
+
SimpleCov.configure do
|
7
|
+
root File.join(File.dirname(__FILE__), '..')
|
8
|
+
project_name 'Ruby JWT - Ruby JSON Web Token implementation'
|
9
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
10
|
+
SimpleCov::Formatter::HTMLFormatter,
|
11
|
+
SimpleCov::Formatter::JSONFormatter
|
12
|
+
]
|
13
|
+
|
14
|
+
add_filter 'spec'
|
15
|
+
end
|
16
|
+
|
17
|
+
SimpleCov.start if ENV['COVERAGE']
|
18
|
+
|
2
19
|
require "#{File.dirname(__FILE__)}/../lib/jwt.rb"
|
data/spec/jwt_spec.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
require 'helper'
|
2
3
|
|
3
4
|
describe JWT do
|
4
5
|
before do
|
5
|
-
@payload = {'foo' => 'bar', 'exp' => Time.now.to_i + 1, 'nbf' => Time.now.to_i - 1 }
|
6
|
+
@payload = { 'foo' => 'bar', 'exp' => Time.now.to_i + 1, 'nbf' => Time.now.to_i - 1 }
|
6
7
|
end
|
7
8
|
|
8
9
|
it 'encodes and decodes JWTs' do
|
@@ -51,7 +52,7 @@ describe JWT do
|
|
51
52
|
|
52
53
|
it 'encodes and decodes JWTs with custom header fields' do
|
53
54
|
private_key = OpenSSL::PKey::RSA.generate(512)
|
54
|
-
jwt = JWT.encode(@payload, private_key, 'RS256',
|
55
|
+
jwt = JWT.encode(@payload, private_key, 'RS256', 'kid' => 'default')
|
55
56
|
decoded_payload = JWT.decode(jwt) do |header|
|
56
57
|
expect(header['kid']).to eq('default')
|
57
58
|
private_key.public_key
|
@@ -68,40 +69,65 @@ describe JWT do
|
|
68
69
|
end
|
69
70
|
|
70
71
|
it 'decodes valid JWTs' do
|
71
|
-
example_payload = {'hello' => 'world'}
|
72
|
+
example_payload = { 'hello' => 'world' }
|
72
73
|
example_secret = 'secret'
|
73
74
|
example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
|
74
75
|
decoded_payload = JWT.decode(example_jwt, example_secret)
|
75
76
|
expect(decoded_payload).to include(example_payload)
|
76
77
|
end
|
77
78
|
|
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
|
87
|
+
|
78
88
|
it 'decodes valid JWTs with iss' do
|
79
|
-
example_payload = {'hello' => 'world', 'iss' => 'jwtiss'}
|
89
|
+
example_payload = { 'hello' => 'world', 'iss' => 'jwtiss' }
|
80
90
|
example_secret = 'secret'
|
81
91
|
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaXNzIjoiand0aXNzIn0.nTZkyYfpGUyKULaj45lXw_1gXXjHvGW4h5V7okHdUqQ'
|
82
|
-
decoded_payload = JWT.decode(example_jwt, example_secret, true,
|
92
|
+
decoded_payload = JWT.decode(example_jwt, example_secret, true, 'iss' => 'jwtiss')
|
83
93
|
expect(decoded_payload).to include(example_payload)
|
84
94
|
end
|
85
95
|
|
86
|
-
|
87
|
-
|
88
|
-
|
96
|
+
context 'issuer claim verifications' do
|
97
|
+
it 'raises invalid issuer when "iss" claim does not match' do
|
98
|
+
example_secret = 'secret'
|
89
99
|
|
90
|
-
|
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
|
91
103
|
|
92
|
-
|
93
|
-
|
104
|
+
it 'raises invalid issuer when "iss" claim is missing in payload' do
|
105
|
+
example_secret = 'secret'
|
106
|
+
|
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
|
+
end
|
110
|
+
|
111
|
+
it 'does not raise invalid issuer when verify_iss is set to false (default option)' do
|
112
|
+
example_secret = 'secret'
|
113
|
+
|
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
|
117
|
+
|
118
|
+
it 'does not raise invalid issuer when correct "iss" is in payload' do
|
119
|
+
example_secret = 'secret'
|
94
120
|
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
123
|
+
end
|
98
124
|
end
|
99
125
|
|
100
126
|
it 'decodes valid JWTs with iat' do
|
101
|
-
example_payload = {'hello' => 'world', 'iat' => 1425917209}
|
127
|
+
example_payload = { 'hello' => 'world', 'iat' => 1425917209 }
|
102
128
|
example_secret = 'secret'
|
103
129
|
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5fQ.m4F-Ugo7aLnLunBBO3BeDidyWMx8T9eoJz6FW2rgQhU'
|
104
|
-
decoded_payload = JWT.decode(example_jwt, example_secret, true,
|
130
|
+
decoded_payload = JWT.decode(example_jwt, example_secret, true, 'iat' => true)
|
105
131
|
expect(decoded_payload).to include(example_payload)
|
106
132
|
end
|
107
133
|
|
@@ -109,14 +135,14 @@ describe JWT do
|
|
109
135
|
# example_payload = {'hello' => 'world', 'iat' => 'abc'}
|
110
136
|
example_secret = 'secret'
|
111
137
|
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoiMTQyNTkxNzIwOSJ9.Mn_vk61xWjIhbXFqAB0nFmNkDiCmfzUgl_LaCKRT6S8'
|
112
|
-
expect{ JWT.decode(example_jwt, example_secret, true,
|
138
|
+
expect { JWT.decode(example_jwt, example_secret, true, :verify_iat => true, 'iat' => 1425917209) }.to raise_error(JWT::InvalidIatError)
|
113
139
|
end
|
114
140
|
|
115
141
|
it 'decodes valid JWTs with jti' do
|
116
|
-
example_payload = {'hello' => 'world', 'iat' => 1425917209, 'jti' => Digest::MD5.hexdigest('secret:1425917209')}
|
142
|
+
example_payload = { 'hello' => 'world', 'iat' => 1425917209, 'jti' => Digest::MD5.hexdigest('secret:1425917209') }
|
117
143
|
example_secret = 'secret'
|
118
144
|
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5LCJqdGkiOiI1NWM3NzZlMjFmN2NiZDg3OWMwNmZhYzAxOGRhYzQwMiJ9.ET0hb-VTUOL3M22oG13ofzvGPLMAncbF8rdNDIqo8tg'
|
119
|
-
decoded_payload = JWT.decode(example_jwt, example_secret, true,
|
145
|
+
decoded_payload = JWT.decode(example_jwt, example_secret, true, 'jti' => Digest::MD5.hexdigest('secret:1425917209'))
|
120
146
|
expect(decoded_payload).to include(example_payload)
|
121
147
|
end
|
122
148
|
|
@@ -124,7 +150,7 @@ describe JWT do
|
|
124
150
|
# example_payload = {'hello' => 'world', 'iat' => 1425917209, 'jti' => Digest::MD5.hexdigest('secret:1425917209')}
|
125
151
|
example_secret = 'secret'
|
126
152
|
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5LCJqdGkiOiI1NWM3NzZlMjFmN2NiZDg3OWMwNmZhYzAxOGRhYzQwMiJ9.ET0hb-VTUOL3M22oG13ofzvGPLMAncbF8rdNDIqo8tg'
|
127
|
-
expect{ JWT.decode(example_jwt, example_secret, true,
|
153
|
+
expect { JWT.decode(example_jwt, example_secret, true, :verify_jti => true, 'jti' => Digest::MD5.hexdigest('secret:1425922032')) }.to raise_error(JWT::InvalidJtiError)
|
128
154
|
# expect{ JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::InvalidJtiError)
|
129
155
|
end
|
130
156
|
|
@@ -132,43 +158,46 @@ describe JWT do
|
|
132
158
|
# example_payload = {'hello' => 'world', 'jti' => Digest::MD5.hexdigest('secret:1425917209')}
|
133
159
|
example_secret = 'secret'
|
134
160
|
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwianRpIjoiNTVjNzc2ZTIxZjdjYmQ4NzljMDZmYWMwMThkYWM0MDIifQ.n0foJCnCM_-_xUvG_TOmR9mYpL2y0UqZOD_gv33djeE'
|
135
|
-
expect{ JWT.decode(example_jwt, example_secret, true,
|
136
|
-
end
|
161
|
+
expect { JWT.decode(example_jwt, example_secret, true, :verify_jti => true, 'jti' => Digest::MD5.hexdigest('secret:1425922032')) }.to raise_error(JWT::InvalidJtiError)
|
162
|
+
end
|
163
|
+
|
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
|
137
176
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
decoded_payload = JWT.decode(example_jwt, example_secret, true, {'aud' => 'url:pnd'})
|
145
|
-
decoded_payload2 = JWT.decode(example_jwt2, example_secret, true, {'aud' => 'url:pnd'})
|
146
|
-
expect(decoded_payload).to include(example_payload)
|
147
|
-
expect(decoded_payload2).to include(example_payload2)
|
148
|
-
end
|
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)
|
182
|
+
end
|
149
183
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
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)
|
189
|
+
end
|
155
190
|
end
|
156
191
|
|
157
192
|
it 'decodes valid JWTs with sub' do
|
158
|
-
example_payload = {'hello' => 'world', 'sub' => 'subject'}
|
193
|
+
example_payload = { 'hello' => 'world', 'sub' => 'subject' }
|
159
194
|
example_secret = 'secret'
|
160
195
|
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwic3ViIjoic3ViamVjdCJ9.QUnNVZm4SPB4vP2zY9m1LoUSOx-5oGXBhj7R89D_UtA'
|
161
|
-
decoded_payload = JWT.decode(example_jwt, example_secret, true,
|
196
|
+
decoded_payload = JWT.decode(example_jwt, example_secret, true, 'sub' => 'subject')
|
162
197
|
expect(decoded_payload).to include(example_payload)
|
163
198
|
end
|
164
199
|
|
165
|
-
it 'raise decode exception when the sub is invalid'
|
166
|
-
# example_payload = {'hello' => 'world', 'sub' => 'subject'}
|
167
|
-
# TODO: Test not working
|
168
|
-
example_secret = 'secret'
|
169
|
-
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwic3ViIjoic3ViamVjdCJ9.QUnNVZm4SPB4vP2zY9m1LoUSOx-5oGXBhj7R89D_UtA'
|
170
|
-
# expect{ JWT.decode(example_jwt, example_secret, true, {:verify_iss => true, 'iss' => 'subject'}) }.to raise_error(JWT::InvalidSubError)
|
171
|
-
end
|
200
|
+
it 'raise decode exception when the sub is invalid'
|
172
201
|
|
173
202
|
it 'raises decode exception when the token is invalid' do
|
174
203
|
example_secret = 'secret'
|
@@ -312,7 +341,7 @@ describe JWT do
|
|
312
341
|
expired_payload['exp'] = Time.now.to_i - 1
|
313
342
|
secret = 'secret'
|
314
343
|
jwt = JWT.encode(expired_payload, secret)
|
315
|
-
decoded_payload = JWT.decode(jwt, secret, true,
|
344
|
+
decoded_payload = JWT.decode(jwt, secret, true, :verify_expiration => false)
|
316
345
|
expect(decoded_payload).to include(expired_payload)
|
317
346
|
end
|
318
347
|
|
@@ -321,7 +350,7 @@ describe JWT do
|
|
321
350
|
expired_payload['exp'] = Time.now.to_i - 2
|
322
351
|
secret = 'secret'
|
323
352
|
jwt = JWT.encode(expired_payload, secret)
|
324
|
-
decoded_payload = JWT.decode(jwt, secret, true,
|
353
|
+
decoded_payload = JWT.decode(jwt, secret, true, :leeway => 3)
|
325
354
|
expect(decoded_payload).to include(expired_payload)
|
326
355
|
end
|
327
356
|
|
@@ -337,7 +366,7 @@ describe JWT do
|
|
337
366
|
mature_payload = @payload.clone
|
338
367
|
secret = 'secret'
|
339
368
|
jwt = JWT.encode(mature_payload, secret)
|
340
|
-
decoded_payload = JWT.decode(jwt, secret, true,
|
369
|
+
decoded_payload = JWT.decode(jwt, secret, true, :verify_expiration => false)
|
341
370
|
expect(decoded_payload).to include(mature_payload)
|
342
371
|
end
|
343
372
|
|
@@ -354,7 +383,7 @@ describe JWT do
|
|
354
383
|
immature_payload['nbf'] = Time.now.to_i + 2
|
355
384
|
secret = 'secret'
|
356
385
|
jwt = JWT.encode(immature_payload, secret)
|
357
|
-
decoded_payload = JWT.decode(jwt, secret, true,
|
386
|
+
decoded_payload = JWT.decode(jwt, secret, true, :verify_not_before => false)
|
358
387
|
expect(decoded_payload).to include(immature_payload)
|
359
388
|
end
|
360
389
|
|
@@ -363,7 +392,7 @@ describe JWT do
|
|
363
392
|
immature_payload['nbf'] = Time.now.to_i - 2
|
364
393
|
secret = 'secret'
|
365
394
|
jwt = JWT.encode(immature_payload, secret)
|
366
|
-
decoded_payload = JWT.decode(jwt, secret, true,
|
395
|
+
decoded_payload = JWT.decode(jwt, secret, true, :leeway => 3)
|
367
396
|
expect(decoded_payload).to include(immature_payload)
|
368
397
|
end
|
369
398
|
|
@@ -402,13 +431,13 @@ xwIDAQAB
|
|
402
431
|
-----END PUBLIC KEY-----
|
403
432
|
PUBKEY
|
404
433
|
jwt = (
|
405
|
-
'eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiY'
|
406
|
-
'XVkIjoiMTA2MDM1Nzg5MTY4OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSI'
|
407
|
-
'sImNpZCI6IjEwNjAzNTc4OTE2ODguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb'
|
408
|
-
'20iLCJpZCI6IjExNjQ1MjgyNDMwOTg1Njc4MjE2MyIsInRva2VuX2hhc2giOiJ'
|
409
|
-
'0Z2hEOUo4bjhWME4ydmN3NmVNaWpnIiwiaWF0IjoxMzIwNjcwOTc4LCJleHAiO'
|
410
|
-
'jEzMjA2NzQ4Nzh9.D8x_wirkxDElqKdJBcsIws3Ogesk38okz6MN7zqC7nEAA7'
|
411
|
-
'wcy1PxsROY1fmBvXSer0IQesAqOW-rPOCNReSn-eY8d53ph1x2HAF-AzEi3GOl'
|
434
|
+
'eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiY' \
|
435
|
+
'XVkIjoiMTA2MDM1Nzg5MTY4OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSI' \
|
436
|
+
'sImNpZCI6IjEwNjAzNTc4OTE2ODguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb' \
|
437
|
+
'20iLCJpZCI6IjExNjQ1MjgyNDMwOTg1Njc4MjE2MyIsInRva2VuX2hhc2giOiJ' \
|
438
|
+
'0Z2hEOUo4bjhWME4ydmN3NmVNaWpnIiwiaWF0IjoxMzIwNjcwOTc4LCJleHAiO' \
|
439
|
+
'jEzMjA2NzQ4Nzh9.D8x_wirkxDElqKdJBcsIws3Ogesk38okz6MN7zqC7nEAA7' \
|
440
|
+
'wcy1PxsROY1fmBvXSer0IQesAqOW-rPOCNReSn-eY8d53ph1x2HAF-AzEi3GOl' \
|
412
441
|
'6hFycH8wj7Su6JqqyEbIVLxE7q7DkAZGaMPkxbTHs1EhSd5_oaKQ6O4xO3ZnnT4'
|
413
442
|
)
|
414
443
|
expect { JWT.decode(jwt, pubkey, true) }.to raise_error(JWT::DecodeError)
|
@@ -427,7 +456,7 @@ PUBKEY
|
|
427
456
|
jwt = JWT.encode(@payload, secret)
|
428
457
|
decoded_segments = JWT.decoded_segments(jwt)
|
429
458
|
expect(decoded_segments.size).to eq(4)
|
430
|
-
expect(decoded_segments[0]).to eq(
|
459
|
+
expect(decoded_segments[0]).to eq('typ' => 'JWT', 'alg' => 'HS256')
|
431
460
|
expect(decoded_segments[1]).to eq(@payload)
|
432
461
|
end
|
433
462
|
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.5.
|
4
|
+
version: 1.5.1
|
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-06-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: echoe
|