jwt 0.1.13 → 1.0.0

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.
Files changed (7) hide show
  1. data/Manifest +1 -0
  2. data/Rakefile +1 -2
  3. data/jwt.gemspec +4 -7
  4. data/lib/jwt.rb +66 -36
  5. data/lib/jwt/json.rb +30 -0
  6. data/spec/jwt_spec.rb +46 -26
  7. metadata +4 -18
data/Manifest CHANGED
@@ -1,5 +1,6 @@
1
1
  Rakefile
2
2
  lib/jwt.rb
3
+ lib/jwt/json.rb
3
4
  spec/helper.rb
4
5
  spec/jwt_spec.rb
5
6
  Manifest
data/Rakefile CHANGED
@@ -2,13 +2,12 @@ require 'rubygems'
2
2
  require 'rake'
3
3
  require 'echoe'
4
4
 
5
- Echoe.new('jwt', '0.1.13') do |p|
5
+ Echoe.new('jwt', '1.0.0') do |p|
6
6
  p.description = "JSON Web Token implementation in Ruby"
7
7
  p.url = "http://github.com/progrium/ruby-jwt"
8
8
  p.author = "Jeff Lindsay"
9
9
  p.email = "progrium@gmail.com"
10
10
  p.ignore_pattern = ["tmp/*"]
11
- p.runtime_dependencies = ["multi_json >=1.5"]
12
11
  p.development_dependencies = ["echoe >=4.6.3"]
13
12
  p.licenses = "MIT"
14
13
  end
@@ -2,15 +2,15 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "jwt"
5
- s.version = "0.1.13"
5
+ s.version = "1.0.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Jeff Lindsay"]
9
- s.date = "2014-05-09"
9
+ s.date = "2014-05-07"
10
10
  s.description = "JSON Web Token implementation in Ruby"
11
11
  s.email = "progrium@gmail.com"
12
- s.extra_rdoc_files = ["lib/jwt.rb"]
13
- s.files = ["Rakefile", "lib/jwt.rb", "spec/helper.rb", "spec/jwt_spec.rb", "Manifest", "jwt.gemspec"]
12
+ s.extra_rdoc_files = ["lib/jwt.rb", "lib/jwt/json.rb"]
13
+ s.files = ["Rakefile", "lib/jwt.rb", "lib/jwt/json.rb", "spec/helper.rb", "spec/jwt_spec.rb", "Manifest", "jwt.gemspec"]
14
14
  s.homepage = "http://github.com/progrium/ruby-jwt"
15
15
  s.licenses = ["MIT"]
16
16
  s.rdoc_options = ["--line-numbers", "--title", "Jwt", "--main", "README.md"]
@@ -23,14 +23,11 @@ Gem::Specification.new do |s|
23
23
  s.specification_version = 3
24
24
 
25
25
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
26
- s.add_runtime_dependency(%q<multi_json>, [">= 1.5"])
27
26
  s.add_development_dependency(%q<echoe>, [">= 4.6.3"])
28
27
  else
29
- s.add_dependency(%q<multi_json>, [">= 1.5"])
30
28
  s.add_dependency(%q<echoe>, [">= 4.6.3"])
31
29
  end
32
30
  else
33
- s.add_dependency(%q<multi_json>, [">= 1.5"])
34
31
  s.add_dependency(%q<echoe>, [">= 4.6.3"])
35
32
  end
36
33
  end
data/lib/jwt.rb CHANGED
@@ -6,10 +6,11 @@
6
6
 
7
7
  require "base64"
8
8
  require "openssl"
9
- require "multi_json"
9
+ require "jwt/json"
10
10
 
11
11
  module JWT
12
12
  class DecodeError < StandardError; end
13
+ extend JWT::Json
13
14
 
14
15
  module_function
15
16
 
@@ -44,59 +45,88 @@ module JWT
44
45
  Base64.encode64(str).tr("+/", "-_").gsub(/[\n=]/, "")
45
46
  end
46
47
 
47
- def encode(payload, key, algorithm="HS256", header_fields={})
48
- algorithm ||= "none"
49
- segments = []
48
+ def encoded_header(algorithm="HS256", header_fields={})
50
49
  header = {"typ" => "JWT", "alg" => algorithm}.merge(header_fields)
51
- segments << base64url_encode(MultiJson.encode(header))
52
- segments << base64url_encode(MultiJson.encode(payload))
53
- signing_input = segments.join(".")
50
+ base64url_encode(encode_json(header))
51
+ end
52
+
53
+ def encoded_payload(payload)
54
+ base64url_encode(encode_json(payload))
55
+ end
56
+
57
+ def encoded_signature(signing_input, key, algorithm)
54
58
  if algorithm == "none"
55
- segments << ""
59
+ ""
56
60
  else
57
61
  signature = sign(algorithm, signing_input, key)
58
- segments << base64url_encode(signature)
62
+ base64url_encode(signature)
59
63
  end
64
+ end
65
+
66
+ def encode(payload, key, algorithm="HS256", header_fields={})
67
+ algorithm ||= "none"
68
+ segments = []
69
+ segments << encoded_header(algorithm, header_fields)
70
+ segments << encoded_payload(payload)
71
+ segments << encoded_signature(segments.join("."), key, algorithm)
60
72
  segments.join(".")
61
73
  end
62
74
 
63
- def decode(jwt, key=nil, verify=true, &keyfinder)
75
+ def raw_segments(jwt, verify=true)
64
76
  segments = jwt.split(".")
65
- raise JWT::DecodeError.new("Not enough or too many segments") unless [2,3].include? segments.length
66
- header_segment, payload_segment, crypto_segment = segments
77
+ required_num_segments = verify ? [3] : [2,3]
78
+ raise JWT::DecodeError.new("Not enough or too many segments") unless required_num_segments.include? segments.length
79
+ segments
80
+ end
81
+
82
+ def decode_header_and_payload(header_segment, payload_segment)
83
+ header = decode_json(base64url_decode(header_segment))
84
+ payload = decode_json(base64url_decode(payload_segment))
85
+ [header, payload]
86
+ end
87
+
88
+ def decoded_segments(jwt, verify=true)
89
+ header_segment, payload_segment, crypto_segment = raw_segments(jwt, verify)
90
+ header, payload = decode_header_and_payload(header_segment, payload_segment)
91
+ signature = base64url_decode(crypto_segment.to_s) if verify
67
92
  signing_input = [header_segment, payload_segment].join(".")
68
- begin
69
- header = MultiJson.decode(base64url_decode(header_segment))
70
- payload = MultiJson.decode(base64url_decode(payload_segment))
71
- signature = base64url_decode(crypto_segment.to_s) if verify
72
- rescue MultiJson::LoadError
73
- raise JWT::DecodeError.new("Invalid segment encoding")
74
- end
93
+ [header, payload, signature, signing_input]
94
+ end
95
+
96
+ def decode(jwt, key=nil, verify=true, &keyfinder)
97
+ raise JWT::DecodeError.new("Nil JSON web token") unless jwt
75
98
 
99
+ header, payload, signature, signing_input = decoded_segments(jwt, verify)
76
100
  raise JWT::DecodeError.new("Not enough or too many segments") unless header && payload
77
101
 
78
102
  if verify
79
- algo = header["alg"]
103
+ algo, key = signature_algorithm_and_key(header, key, &keyfinder)
104
+ verify_signature(algo, key, signing_input, signature)
105
+ end
106
+ return payload,header
107
+ end
80
108
 
81
- if keyfinder
82
- key = keyfinder.call(header)
83
- end
109
+ def signature_algorithm_and_key(header, key, &keyfinder)
110
+ if keyfinder
111
+ key = keyfinder.call(header)
112
+ end
113
+ [header['alg'], key]
114
+ end
84
115
 
85
- begin
86
- if ["HS256", "HS384", "HS512"].include?(algo)
87
- raise JWT::DecodeError.new("Signature verification failed") unless secure_compare(signature, sign_hmac(algo, signing_input, key))
88
- elsif ["RS256", "RS384", "RS512"].include?(algo)
89
- raise JWT::DecodeError.new("Signature verification failed") unless verify_rsa(algo, key, signing_input, signature)
90
- else
91
- raise JWT::DecodeError.new("Algorithm not supported")
92
- end
93
- rescue OpenSSL::PKey::PKeyError
94
- raise JWT::DecodeError.new("Signature verification failed")
95
- ensure
96
- OpenSSL.errors.clear
116
+ def verify_signature(algo, key, signing_input, signature)
117
+ begin
118
+ if ["HS256", "HS384", "HS512"].include?(algo)
119
+ raise JWT::DecodeError.new("Signature verification failed") unless secure_compare(signature, sign_hmac(algo, signing_input, key))
120
+ elsif ["RS256", "RS384", "RS512"].include?(algo)
121
+ raise JWT::DecodeError.new("Signature verification failed") unless verify_rsa(algo, key, signing_input, signature)
122
+ else
123
+ raise JWT::DecodeError.new("Algorithm not supported")
97
124
  end
125
+ rescue OpenSSL::PKey::PKeyError
126
+ raise JWT::DecodeError.new("Signature verification failed")
127
+ ensure
128
+ OpenSSL.errors.clear
98
129
  end
99
- payload
100
130
  end
101
131
 
102
132
  # From devise
@@ -0,0 +1,30 @@
1
+ module JWT
2
+ module Json
3
+ if RUBY_VERSION >= "1.9" && !defined?(MultiJson)
4
+ require 'json'
5
+
6
+ def decode_json(encoded)
7
+ JSON.parse(encoded)
8
+ rescue JSON::ParserError
9
+ raise JWT::DecodeError.new("Invalid segment encoding")
10
+ end
11
+
12
+ def encode_json(raw)
13
+ JSON.generate(raw)
14
+ end
15
+
16
+ else
17
+ require "multi_json"
18
+
19
+ def decode_json(encoded)
20
+ MultiJson.decode(encoded)
21
+ rescue MultiJson::LoadError
22
+ raise JWT::DecodeError.new("Invalid segment encoding")
23
+ end
24
+
25
+ def encode_json(raw)
26
+ MultiJson.encode(raw)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -9,24 +9,24 @@ describe JWT do
9
9
  secret = "secret"
10
10
  jwt = JWT.encode(@payload, secret)
11
11
  decoded_payload = JWT.decode(jwt, secret)
12
- decoded_payload.should == @payload
12
+ expect(decoded_payload).to include(@payload)
13
13
  end
14
14
 
15
15
  it "encodes and decodes JWTs for RSA signatures" do
16
16
  private_key = OpenSSL::PKey::RSA.generate(512)
17
17
  jwt = JWT.encode(@payload, private_key, "RS256")
18
18
  decoded_payload = JWT.decode(jwt, private_key.public_key)
19
- decoded_payload.should == @payload
19
+ expect(decoded_payload).to include(@payload)
20
20
  end
21
21
 
22
22
  it "encodes and decodes JWTs with custom header fields" do
23
23
  private_key = OpenSSL::PKey::RSA.generate(512)
24
24
  jwt = JWT.encode(@payload, private_key, "RS256", {"kid" => 'default'})
25
25
  decoded_payload = JWT.decode(jwt) do |header|
26
- header["kid"].should == 'default'
26
+ expect(header["kid"]).to eq('default')
27
27
  private_key.public_key
28
28
  end
29
- decoded_payload.should == @payload
29
+ expect(decoded_payload).to include(@payload)
30
30
  end
31
31
 
32
32
  it "decodes valid JWTs" do
@@ -34,43 +34,46 @@ describe JWT do
34
34
  example_secret = 'secret'
35
35
  example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
36
36
  decoded_payload = JWT.decode(example_jwt, example_secret)
37
- decoded_payload.should == example_payload
37
+ expect(decoded_payload).to include(example_payload)
38
38
  end
39
39
 
40
40
  it "raises exception when the token is invalid" do
41
41
  example_secret = 'secret'
42
42
  # Same as above exmaple with some random bytes replaced
43
43
  example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHiMomlwIjogIkJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
44
- lambda { JWT.decode(example_jwt, example_secret) }.should raise_error(JWT::DecodeError)
44
+ expect { JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::DecodeError)
45
45
  end
46
46
 
47
47
  it "raises exception with wrong hmac key" do
48
48
  right_secret = 'foo'
49
49
  bad_secret = 'bar'
50
50
  jwt_message = JWT.encode(@payload, right_secret, "HS256")
51
- lambda { JWT.decode(jwt_message, bad_secret) }.should raise_error(JWT::DecodeError)
51
+ expect { JWT.decode(jwt_message, bad_secret) }.to raise_error(JWT::DecodeError)
52
52
  end
53
53
 
54
54
  it "raises exception with wrong rsa key" do
55
55
  right_private_key = OpenSSL::PKey::RSA.generate(512)
56
56
  bad_private_key = OpenSSL::PKey::RSA.generate(512)
57
57
  jwt = JWT.encode(@payload, right_private_key, "RS256")
58
- lambda { JWT.decode(jwt, bad_private_key.public_key) }.should raise_error(JWT::DecodeError)
58
+ expect { JWT.decode(jwt, bad_private_key.public_key) }.to raise_error(JWT::DecodeError)
59
59
  end
60
60
 
61
61
  it "raises exception with invalid signature" do
62
- example_payload = {"hello" => "world"}
63
62
  example_secret = 'secret'
64
63
  example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.'
65
- lambda { JWT.decode(example_jwt, example_secret) }.should raise_error(JWT::DecodeError)
64
+ expect { JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::DecodeError)
66
65
  end
67
66
 
68
67
  it "raises exception with nonexistent header" do
69
- lambda { JWT.decode("..stuff") }.should raise_error(JWT::DecodeError)
68
+ expect { JWT.decode("..stuff") }.to raise_error(JWT::DecodeError)
70
69
  end
71
70
 
72
71
  it "raises exception with nonexistent payload" do
73
- lambda { JWT.decode("eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9..stuff") }.should raise_error(JWT::DecodeError)
72
+ expect { JWT.decode("eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9..stuff") }.to raise_error(JWT::DecodeError)
73
+ end
74
+
75
+ it "raises exception with nil jwt" do
76
+ expect { JWT.decode(nil) }.to raise_error(JWT::DecodeError)
74
77
  end
75
78
 
76
79
  it "allows decoding without key" do
@@ -78,7 +81,7 @@ describe JWT do
78
81
  bad_secret = 'bar'
79
82
  jwt = JWT.encode(@payload, right_secret)
80
83
  decoded_payload = JWT.decode(jwt, bad_secret, false)
81
- decoded_payload.should == @payload
84
+ expect(decoded_payload).to include(@payload)
82
85
  end
83
86
 
84
87
  it "checks the key when verify is truthy" do
@@ -86,18 +89,24 @@ describe JWT do
86
89
  bad_secret = 'bar'
87
90
  jwt = JWT.encode(@payload, right_secret)
88
91
  verify = "yes" =~ /^y/i
89
- lambda { JWT.decode(jwt, bad_secret, verify) }.should raise_error(JWT::DecodeError)
92
+ expect { JWT.decode(jwt, bad_secret, verify) }.to raise_error(JWT::DecodeError)
90
93
  end
91
94
 
92
95
  it "raises exception on unsupported crypto algorithm" do
93
- lambda { JWT.encode(@payload, "secret", 'HS1024') }.should raise_error(NotImplementedError)
96
+ expect { JWT.encode(@payload, "secret", 'HS1024') }.to raise_error(NotImplementedError)
94
97
  end
95
98
 
96
99
  it "encodes and decodes plaintext JWTs" do
97
100
  jwt = JWT.encode(@payload, nil, nil)
98
- jwt.split('.').length.should == 2
101
+ expect(jwt.split('.').length).to eq(2)
99
102
  decoded_payload = JWT.decode(jwt, nil, nil)
100
- decoded_payload.should == @payload
103
+ expect(decoded_payload).to include(@payload)
104
+ end
105
+
106
+ it "requires a signature segment when verify is truthy" do
107
+ jwt = JWT.encode(@payload, nil, nil)
108
+ expect(jwt.split('.').length).to eq(2)
109
+ expect { JWT.decode(jwt, nil, true) }.to raise_error(JWT::DecodeError)
101
110
  end
102
111
 
103
112
  it "does not use == to compare digests" do
@@ -106,9 +115,9 @@ describe JWT do
106
115
  crypto_segment = jwt.split(".").last
107
116
 
108
117
  signature = JWT.base64url_decode(crypto_segment)
109
- signature.should_not_receive('==')
110
- JWT.should_receive(:base64url_decode).with(crypto_segment).once.and_return(signature)
111
- JWT.should_receive(:base64url_decode).at_least(:once).and_call_original
118
+ expect(signature).not_to receive('==')
119
+ expect(JWT).to receive(:base64url_decode).with(crypto_segment).once.and_return(signature)
120
+ expect(JWT).to receive(:base64url_decode).at_least(:once).and_call_original
112
121
 
113
122
  JWT.decode(jwt, secret)
114
123
  end
@@ -125,14 +134,14 @@ describe JWT do
125
134
  end
126
135
  end
127
136
 
128
- it "retuns falise of the strings are different" do
129
- expect(JWT.secure_compare("Foo", "Bar")).to be_false
137
+ it "retuns false if the strings are different" do
138
+ expect(JWT.secure_compare("Foo", "Bar")).to be_false
130
139
  end
131
140
  end
132
141
 
133
142
  # no method should leave OpenSSL.errors populated
134
143
  after do
135
- OpenSSL.errors.should be_empty
144
+ expect(OpenSSL.errors).to be_empty
136
145
  end
137
146
 
138
147
  it "raise exception on invalid signature" do
@@ -157,13 +166,24 @@ PUBKEY
157
166
  'wcy1PxsROY1fmBvXSer0IQesAqOW-rPOCNReSn-eY8d53ph1x2HAF-AzEi3GOl' +
158
167
  '6hFycH8wj7Su6JqqyEbIVLxE7q7DkAZGaMPkxbTHs1EhSd5_oaKQ6O4xO3ZnnT4'
159
168
  )
160
- lambda { JWT.decode(jwt, pubkey, true) }.should raise_error(JWT::DecodeError)
169
+ expect { JWT.decode(jwt, pubkey, true) }.to raise_error(JWT::DecodeError)
161
170
  end
162
171
 
163
172
  describe "urlsafe base64 encoding" do
164
173
  it "replaces + and / with - and _" do
165
- Base64.stub(:encode64) { "string+with/non+url-safe/characters_" }
166
- JWT.base64url_encode("foo").should == "string-with_non-url-safe_characters_"
174
+ allow(Base64).to receive(:encode64) { "string+with/non+url-safe/characters_" }
175
+ expect(JWT.base64url_encode("foo")).to eq("string-with_non-url-safe_characters_")
176
+ end
177
+ end
178
+
179
+ describe 'decoded_segments' do
180
+ it "allows access to the decoded header and payload" do
181
+ secret = "secret"
182
+ jwt = JWT.encode(@payload, secret)
183
+ decoded_segments = JWT.decoded_segments(jwt)
184
+ expect(decoded_segments.size).to eq(4)
185
+ expect(decoded_segments[0]).to eq({"typ" => "JWT", "alg" => "HS256"})
186
+ expect(decoded_segments[1]).to eq(@payload)
167
187
  end
168
188
  end
169
189
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jwt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.13
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,24 +9,8 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-05-09 00:00:00.000000000 Z
12
+ date: 2014-05-07 00:00:00.000000000 Z
13
13
  dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: multi_json
16
- requirement: !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '1.5'
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ! '>='
28
- - !ruby/object:Gem::Version
29
- version: '1.5'
30
14
  - !ruby/object:Gem::Dependency
31
15
  name: echoe
32
16
  requirement: !ruby/object:Gem::Requirement
@@ -49,9 +33,11 @@ executables: []
49
33
  extensions: []
50
34
  extra_rdoc_files:
51
35
  - lib/jwt.rb
36
+ - lib/jwt/json.rb
52
37
  files:
53
38
  - Rakefile
54
39
  - lib/jwt.rb
40
+ - lib/jwt/json.rb
55
41
  - spec/helper.rb
56
42
  - spec/jwt_spec.rb
57
43
  - Manifest