jwt-rb 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 02d0845090b2815ed11a6036bad8cc226b847827
4
+ data.tar.gz: 72cf2075c4c70df5ddd29b84df559257e49556f0
5
+ SHA512:
6
+ metadata.gz: 71305e4d66010d9facdfff2ed64723958f343c2a27d9d6dbb1bfc489b5a2a993cb3f4850698077d49db8acfd82d9fa0307cda87d8ce9cb787e6a87f577ffcfc2
7
+ data.tar.gz: a574f05e8dc92466c52d90ac48695ca1ea34fb339a218008cc33a5457ec7b83fa1ca29a1a906c06522952434956c9301d54207566b57002c185c8c7d7e6a33e7
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.1
4
+ script: "bundle exec rspec spec"
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in jwt-rb.gemspec
4
+ gemspec
5
+ gem 'hashie'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Luismi Ramirez
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # Jwt-rb
2
+ [![Code Climate](https://codeclimate.com/github/chupipandi/jwt-rb.png)](https://codeclimate.com/github/chupipandi/jwt-rb) [![Build Status](https://travis-ci.org/chupipandi/jwt-rb.svg?branch=master)](https://travis-ci.org/chupipandi/jwt-rb)
3
+
4
+ ## Installation (Coming soon to ruby gems)
5
+
6
+ Add this line to your application's Gemfile
7
+
8
+ gem 'jwt-rb'
9
+
10
+ And then execute:
11
+
12
+ $ bundle
13
+
14
+ Or install it yourself as:
15
+
16
+ $ gem install jwt-rb
17
+
18
+ ## Usage
19
+
20
+ `jwt-rb` has two methods, one for encoding, one for decoding:
21
+
22
+ ###JWT.encode(payload, key, options)
23
+
24
+ `payload` is a hash which contains the data we want to embed into the token.
25
+
26
+ `key` could be a secret string to use in `HMAC` algorithms or a private key for `RSA` algorithms.
27
+
28
+ `options`:
29
+
30
+ * `algorithm`: Algorithm to use, default is `HS256`.
31
+ * `claims` (They are optional):
32
+ * `exp`: Time until the token expires, in seconds.
33
+ * `aud`: Intended audience of the token.
34
+ * `iss`: Identifies the issuer of the token.
35
+
36
+ ####Examples:
37
+
38
+ Basic token:
39
+ ```ruby
40
+ token = JWT.encode({hello: 'world'}, 'thisismysecret')
41
+ ```
42
+
43
+ Token with expiration (24 hours):
44
+ ```ruby
45
+ token = JWT.encode({hello: 'world'}, 'thisismysecret', claim: { exp: 86400 })
46
+ ```
47
+
48
+ Token with RSA key:
49
+ ```ruby
50
+ private_key = OpenSSL::PKey::RSA.generate(512)
51
+ token = JWT.encode({hello: 'world'}, key, algorithm: 'RS256')
52
+ ```
53
+
54
+ ###JWT.decode(token, key, options)
55
+
56
+ `token` is the JWT string we encoded before.
57
+
58
+ `key` could be a secret string to use in `HMAC` algorithms or a private key for `RSA` algorithms.
59
+
60
+ `options`:
61
+
62
+ * `algorithm` : Algorithm to be used to verify the token.
63
+ * `claims`:
64
+ * `aud`: Audience of the token to verify if it matches with the token.
65
+ * `iss`: Issuer of the token to verify if it matches with the token.
66
+
67
+ ####Examples:
68
+
69
+ Basic decoding:
70
+ ```ruby
71
+ jwt = 'string containing real jwt token'
72
+ payload, header = JWT.decode(jwt, 'thisismysecret')
73
+ ```
74
+
75
+ Decoding with RSA:
76
+ ```ruby
77
+ # private key used to sign the token
78
+ private_key = OpenSSL::PKey::RSA.generate(512)
79
+ jwt = 'string containing real jwt token'
80
+ payload, header = JWT.decode(jwt, private_key.public_key, algorithm: 'RS256')
81
+ ```
82
+
83
+ Decoding a token with claims:
84
+ ```ruby
85
+ jwt = 'string containing real jwt token'
86
+ payload, header = JWT.decode(jwt, 'thisismysecret', claims: { aud: 'foo' })
87
+ ```
88
+
89
+ ###Supported Algorithms
90
+
91
+ Algorithm parameter | Algorithm used
92
+ ----------|-------
93
+ HS256 | HMAC using SHA-256 algorithm
94
+ HS384 | HMAC using SHA-384 algorithm
95
+ HS512 | HMAC using SHA-512 algorithm
96
+ RS256 | RSA using SHA-256 algorithm
97
+ RS384 | RSA using SHA-384 algorithm
98
+ RS512 | RSA using SHA-512 algorithm
99
+
100
+ ## Contributing
101
+
102
+ 1. Fork it ( https://github.com/chupipandi/jwt-rb/fork )
103
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
104
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
105
+ 4. Push to the branch (`git push origin my-new-feature`)
106
+ 5. Create a new Pull Request
107
+
108
+ ## License
109
+
110
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/jwtrb.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ require File.expand_path('../lib/jwt/version', __FILE__)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = 'jwt-rb'
6
+ spec.version = JWT::VERSION
7
+ spec.authors = ["ChupipandiCrew"]
8
+ spec.email = ["chupi@chupipandi.com"]
9
+ spec.summary = %q{Ruby gem to encode/ decode JWT}
10
+ spec.homepage = "http://github.com/chupipandi/jwt-rb"
11
+ spec.license = "MIT"
12
+
13
+ spec.files = `git ls-files -z`.split("\x0")
14
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
15
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
16
+ spec.require_paths = ["lib"]
17
+
18
+ spec.add_development_dependency "bundler", "~> 1.6"
19
+ spec.add_development_dependency "rake"
20
+ spec.add_development_dependency 'rspec'
21
+ spec.add_development_dependency 'timecop'
22
+ end
@@ -0,0 +1,97 @@
1
+ module JWT
2
+ class Decoder
3
+ include JWT::Verificator
4
+ include JWT::Signature
5
+
6
+ class << self
7
+ def decode(jwt, key, options)
8
+ @algorithm = options[:algorithm] || 'HS256'
9
+ @jwt = jwt
10
+
11
+ validate_jwt!
12
+
13
+ header, payload, input, signature = decode_token
14
+
15
+ verify_claims!(payload, options[:claims])
16
+ verify_signature!(key, signature, input)
17
+
18
+ [payload, header]
19
+ end
20
+
21
+ private
22
+
23
+ def validate_jwt!
24
+ if parse_token.length != 3
25
+ fail JWT::DecoderError.new('Invalid number of segments')
26
+ end
27
+ end
28
+
29
+ def parse_token
30
+ @jwt.split('.')
31
+ end
32
+
33
+ def decode_token
34
+ header, payload, signature = parse_token
35
+
36
+ input = [header, payload].join('.')
37
+ header = decode_header(header)
38
+ payload = Payload.new(decode_payload(payload))
39
+ signature = decode_signature(signature)
40
+
41
+ [header, payload, input, signature]
42
+ end
43
+
44
+ def decode_header(header)
45
+ fail JWT::DecoderError.new('Empty header') if header.empty?
46
+ header = base64_decode(header)
47
+ JSON.parse(header)
48
+ end
49
+
50
+ def decode_payload(payload)
51
+ fail JWT::DecoderError.new('Empty payload') if payload.empty?
52
+ payload = base64_decode(payload)
53
+ JSON.parse(payload)
54
+ end
55
+
56
+ def decode_signature(signature)
57
+ base64_decode(signature)
58
+ end
59
+
60
+ def base64_decode(string)
61
+ string += '=' * (4 - string.length.modulo(4))
62
+ Base64.decode64(string.tr('-_','+/'))
63
+ end
64
+
65
+ def verify_signature!(key, signature, input)
66
+ supported_algorithm!(@algorithm)
67
+
68
+ parsed_algorithm = parse_algorithm(@algorithm)
69
+ digest = generate_digest(parsed_algorithm)
70
+
71
+ if @algorithm =~ /^HS/
72
+ hs_key_format!(key)
73
+ signed_input = sign_hmac(digest, key, input)
74
+
75
+ unless secure_compare(signature, signed_input)
76
+ fail JWT::VerificationError.new('Signature does not match')
77
+ end
78
+ else
79
+ rs_key_format!(key)
80
+ verify_rsa(key, digest, signature, input)
81
+ end
82
+ end
83
+
84
+ # From devise & inspired from ruby-jwt of http://github.com/progrium
85
+ # constant-time comparison algorithm to prevent timing attacks
86
+ def secure_compare(a, b)
87
+ return false if a.nil? || b.nil? || a.empty? || b.empty? || a.bytesize != b.bytesize
88
+ l = a.unpack "C#{a.bytesize}"
89
+
90
+ res = 0
91
+ b.each_byte { |byte| res |= byte ^ l.shift }
92
+ res == 0
93
+ end
94
+ end
95
+ end
96
+ end
97
+
@@ -0,0 +1,69 @@
1
+ module JWT
2
+ class Encoder
3
+ include JWT::Verificator
4
+ include JWT::Signature
5
+
6
+ class << self
7
+ def encode(payload, key, options)
8
+ @algorithm = options[:algorithm] || 'HS256'
9
+ @payload = Payload.new(payload)
10
+
11
+ header = encode_header
12
+ payload = decorate_and_encode_payload(options[:claims] || {})
13
+ signature = encode_signature(header, payload, key)
14
+
15
+ [header, payload, signature].join('.')
16
+ end
17
+
18
+ private
19
+
20
+ def encode_header
21
+ base64_encode(header)
22
+ end
23
+
24
+ def header
25
+ { typ: 'JWT', alg: @algorithm }.to_json
26
+ end
27
+
28
+ def encode_payload
29
+ base64_encode(@payload.to_json)
30
+ end
31
+
32
+ def decorate_and_encode_payload(claims)
33
+ @payload[:iat] = Time.now.to_i
34
+ @payload[:exp] = @payload[:iat] + claims[:exp] if valid_integer_claim(claims[:exp])
35
+ @payload[:aud] = claims[:aud] if valid_string_claim(claims[:aud])
36
+ @payload[:iss] = claims[:iss] if valid_string_claim(claims[:iss])
37
+
38
+ encode_payload
39
+ end
40
+
41
+ def base64_encode(string)
42
+ Base64.encode64(string).tr('+/', '-_').gsub(/[\n=]/, '')
43
+ end
44
+
45
+ def encode_signature(header, payload, key)
46
+ input = [header, payload].join('.')
47
+ signature = sign(input, key)
48
+
49
+ base64_encode(signature)
50
+ end
51
+
52
+ def sign(input, key)
53
+ supported_algorithm!(@algorithm)
54
+
55
+ parsed_algorithm = parse_algorithm(@algorithm)
56
+ digest = generate_digest(parsed_algorithm)
57
+
58
+ if @algorithm =~ /^HS/
59
+ hs_key_format!(key)
60
+ sign_hmac(digest, key, input)
61
+ else
62
+ rs_key_format!(key)
63
+ sign_rsa(key, digest, input)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,10 @@
1
+ module JWT
2
+ class VerificationError < StandardError
3
+ end
4
+
5
+ class InvalidKeyFormatError < StandardError
6
+ end
7
+
8
+ class DecoderError < StandardError
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ require 'hashie'
2
+
3
+ module JWT
4
+ class Payload < Hash
5
+ include Hashie::Extensions::MergeInitializer
6
+ include Hashie::Extensions::IndifferentAccess
7
+ end
8
+ end
@@ -0,0 +1,35 @@
1
+ module JWT
2
+ module Signature
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+
9
+ private
10
+
11
+ def parse_algorithm(algorithm)
12
+ algorithm.sub(/^../, 'sha')
13
+ end
14
+
15
+ def generate_digest(parsed_algorithm)
16
+ OpenSSL::Digest.new(parsed_algorithm)
17
+ end
18
+
19
+ def sign_hmac(digest, key, input)
20
+ OpenSSL::HMAC.digest(digest, key, input)
21
+ end
22
+
23
+ def sign_rsa(private_key, digest, input)
24
+ private_key.sign(digest, input)
25
+ rescue
26
+ JWT::VerificationError.new('Your key needs to be able to .sign()')
27
+ end
28
+
29
+ def verify_rsa(public_key, digest, signature, input)
30
+ fail JWT::VerificationError unless public_key.verify(digest, signature, input)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,57 @@
1
+ module JWT
2
+ module Verificator
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ SUPPORTED_ALGS = %w(HS256 HS384 HS512 RS256 RS384 RS512)
9
+ private
10
+
11
+ def supported_algorithm!(algorithm)
12
+ if !SUPPORTED_ALGS.include? algorithm
13
+ fail NotImplementedError.new("#{algorithm} is not supported")
14
+ end
15
+ end
16
+
17
+ def hs_key_format!(key)
18
+ fail JWT::InvalidKeyFormatError unless key.is_a? String
19
+ end
20
+
21
+ def rs_key_format!(key)
22
+ fail JWT::InvalidKeyFormatError unless key.is_a? OpenSSL::PKey::RSA
23
+ end
24
+
25
+ def valid_integer_claim(claim)
26
+ claim.is_a? Integer
27
+ end
28
+
29
+ def valid_string_claim(claim)
30
+ claim.is_a? String
31
+ end
32
+
33
+ def verify_claims!(payload, options)
34
+ validate_expiration(payload[:exp]) if payload[:exp]
35
+ validate_audience(payload[:aud], options[:aud]) if payload[:aud]
36
+ validate_issuer(payload[:iss], options[:iss]) if payload[:iss]
37
+ end
38
+
39
+ def validate_expiration(exp)
40
+ fail JWT::VerificationError.new('JWT Expired') if Time.now.to_i >= exp
41
+ end
42
+
43
+ def validate_audience(aud, given_aud)
44
+ if given_aud && given_aud != aud
45
+ fail JWT::VerificationError.new("Invalid Audience. Expected: #{aud}")
46
+ end
47
+ end
48
+
49
+ def validate_issuer(iss, given_iss)
50
+ if given_iss && given_iss != iss
51
+ fail JWT::VerificationError.new("Invalid Issuer. Expected: #{iss}")
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+
@@ -0,0 +1,3 @@
1
+ module JWT
2
+ VERSION = "0.0.1"
3
+ end
data/lib/jwt.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'jwt/payload'
2
+ require 'jwt/verificator'
3
+ require 'jwt/signature'
4
+ require 'jwt/encoder'
5
+ require 'jwt/decoder'
6
+ require 'jwt/exceptions'
7
+ require 'json'
8
+ require 'base64'
9
+ require 'openssl'
10
+
11
+ module JWT
12
+ module ModuleFunctions
13
+ def encode(payload, key, options = {})
14
+ Encoder.encode(payload, key, options)
15
+ end
16
+
17
+ def decode(jwt, key, options = {})
18
+ Decoder.decode(jwt, key, options)
19
+ end
20
+ end
21
+
22
+ extend ModuleFunctions
23
+ end
24
+
@@ -0,0 +1,153 @@
1
+ require 'spec_helper'
2
+
3
+ # All jwt used here have been created by external tools to isolate the tests
4
+
5
+ describe 'Decoder' do
6
+ before do
7
+ @payload = { 'hello' => 'world', 'iat' => 946681200 }
8
+ end
9
+
10
+ describe 'decoding' do
11
+ it 'decodes a valid JWT using HS' do
12
+ secret = 'mysecret'
13
+ jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0Ijo5NDY2ODEyMDB9.9OzXVFu0LK4miYiSkWrP5vhzHVtz5DHC8dWalkTah3c'
14
+ payload, header = JWT.decode(jwt, secret)
15
+
16
+ expect(payload).to eq(@payload)
17
+ end
18
+
19
+ it 'decodes a valid JWT using RS' do
20
+ public_key = OpenSSL::PKey::RSA.new(<<-KEY)
21
+ -----BEGIN PUBLIC KEY-----
22
+ MFswDQYJKoZIhvcNAQEBBQADSgAwRwJAUTQbhg/Hcgq26wY9yOWu9L6OX4fqekqo
23
+ R+YhQUXaJGXhW0tNapTdIwTAycby0cUCXoA/7IYi4SXSKMT0owQxeQIDAQAB
24
+ -----END PUBLIC KEY-----
25
+ KEY
26
+
27
+ jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0Ijo5NDY2ODEyMDB9.A0Qiqmo02gEviZtQW8ESC94oKLXcgCSGnktyxDQZNE0AfSoMJZ9JMEWNjEXyp6k7VEm9nn9ZbEu2GdmAIJz-xw'
28
+ payload, header = JWT.decode(jwt, public_key, algorithm: 'RS256')
29
+
30
+ expect(payload).to eq(@payload)
31
+ end
32
+
33
+ describe 'claims' do
34
+ it 'validates a JWT with the correct audience' do
35
+ expected_payload = { 'hello' => 'world', 'iat' => 946681200, 'aud' => 'rubyist' }
36
+ secret = 'mysecret'
37
+ jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0Ijo5NDY2ODEyMDAsImF1ZCI6InJ1Ynlpc3QifQ.u6DB-dpK5zxZUmgprzXtg5yY4djsLLtIv4EAgE_nWSM'
38
+
39
+ payload, header = JWT.decode(jwt, secret, claims: { aud: 'rubyist' })
40
+
41
+ expect(payload).to eq(expected_payload)
42
+ end
43
+
44
+ it 'validates a JWT with expiry if it didnt expire yet' do
45
+ # The expiry is set to 24 hours
46
+ expected_payload = { 'hello' => 'world', 'iat' => 946681200, 'exp' => 946767600}
47
+ secret = 'mysecret'
48
+ jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0Ijo5NDY2ODEyMDAsImV4cCI6OTQ2NzY3NjAwfQ.AZ9a_9L8oY95OT7jc019ZxjWc9fcT5chYsZy6QUgzj0'
49
+
50
+ # Travel 12 hours in the future
51
+ Timecop.travel(Time.at(946724400))
52
+
53
+ payload, header = JWT.decode(jwt, secret)
54
+
55
+ expect(payload).to eq(expected_payload)
56
+ end
57
+
58
+ it 'validates a JWT with the correct issuer' do
59
+ expected_payload = { 'hello' => 'world', 'iat' => 946681200, 'iss' => 'toni' }
60
+ secret = 'mysecret'
61
+ jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0Ijo5NDY2ODEyMDAsImlzcyI6InRvbmkifQ.I4EoPQzt97rehCBnU8VTcDH5R2KX6b8Ta1t5bANGorc'
62
+
63
+ payload, header = JWT.decode(jwt, secret, claims: { iss: 'toni' })
64
+
65
+ expect(payload).to eq(expected_payload)
66
+ end
67
+ end
68
+ end
69
+
70
+ describe 'exceptions' do
71
+ it 'raises an error if you use a string as a key in RS decoding' do
72
+ secret = 'mysecret'
73
+ jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIn0.E0f3M2R4p1_pEmDFp3PZNfXwLp6m3z7MNX468o8gQCYeslnOMdCGvzl1pCJhDs5M7KnaSlvPm_Be3WjYrk8ZDQ'
74
+
75
+ expect { JWT.decode(jwt, secret, algorithm: 'RS256') }
76
+ .to raise_error JWT::InvalidKeyFormatError
77
+ end
78
+
79
+ it 'raises an error if you dont use a string as a key in HS decoding' do
80
+ key = OpenSSL::PKey::RSA.new(<<-KEY)
81
+ -----BEGIN PUBLIC KEY-----
82
+ MFswDQYJKoZIhvcNAQEBBQADSgAwRwJAUTQbhg/Hcgq26wY9yOWu9L6OX4fqekqo
83
+ R+YhQUXaJGXhW0tNapTdIwTAycby0cUCXoA/7IYi4SXSKMT0owQxeQIDAQAB
84
+ -----END PUBLIC KEY-----
85
+ KEY
86
+
87
+ jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIn0.E0f3M2R4p1_pEmDFp3PZNfXwLp6m3z7MNX468o8gQCYeslnOMdCGvzl1pCJhDs5M7KnaSlvPm_Be3WjYrk8ZDQ'
88
+
89
+ expect { JWT.decode(jwt, key) }
90
+ .to raise_error JWT::InvalidKeyFormatError
91
+ end
92
+
93
+ it 'raises an error if the token doesnt have the correct number of segments' do
94
+ secret = 'mysecret'
95
+ jwt = 'header.payload.signature.hello?'
96
+
97
+ expect { JWT.decode(jwt, secret) }
98
+ .to raise_error JWT::DecoderError
99
+ end
100
+
101
+ it 'raises an error if the token cant be verified with the current secret key' do
102
+ secret = 'myothersecret'
103
+ jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIn0.Gc_-AK7EN0nCQj6egXy525yk_cssK2A2lgX-w2NM90M'
104
+
105
+ expect { JWT.decode(jwt, secret) }
106
+ .to raise_error JWT::VerificationError
107
+ end
108
+
109
+ it 'raises an error if there is no header' do
110
+ secret = 'myothersecret'
111
+ jwt = '.eyJoZWxsbyI6IndvcmxkIn0.Gc_-AK7EN0nCQj6egXy525yk_cssK2A2lgX-w2NM90M'
112
+
113
+ expect { JWT.decode(jwt, secret) }
114
+ .to raise_error JWT::DecoderError
115
+ end
116
+
117
+ it 'raises an error if there is no payload' do
118
+ secret = 'myothersecret'
119
+ jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..Gc_-AK7EN0nCQj6egXy525yk_cssK2A2lgX-w2NM90M'
120
+
121
+ expect { JWT.decode(jwt, secret) }
122
+ .to raise_error JWT::DecoderError
123
+ end
124
+
125
+ it 'raises an error if the audience doesnt match' do
126
+ secret = 'mysecret'
127
+ jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0Ijo5NDY2ODEyMDAsImF1ZCI6InJ1Ynlpc3QifQ.u6DB-dpK5zxZUmgprzXtg5yY4djsLLtIv4EAgE_nWSM'
128
+
129
+ expect { JWT.decode(jwt, secret, claims: { aud: 'phpers' })}
130
+ .to raise_error JWT::VerificationError
131
+ end
132
+
133
+ it 'raises an error if the issuer doesnt match' do
134
+ secret = 'mysecret'
135
+ jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0Ijo5NDY2ODEyMDAsImlzcyI6InRvbmkifQ.I4EoPQzt97rehCBnU8VTcDH5R2KX6b8Ta1t5bANGorc'
136
+
137
+ expect { JWT.decode(jwt, secret, claims: { iss: 'john' })}
138
+ .to raise_error JWT::VerificationError
139
+ end
140
+
141
+ it 'raises an error if the token expired' do
142
+ # The expiry is set to 24 hours
143
+ secret = 'mysecret'
144
+ jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0Ijo5NDY2ODEyMDAsImV4cCI6OTQ2NzY3NjAwfQ.AZ9a_9L8oY95OT7jc019ZxjWc9fcT5chYsZy6QUgzj0'
145
+
146
+ # Travel 24 hours, 1 second in the future
147
+ Timecop.travel(Time.at(946767601))
148
+
149
+ expect { JWT.decode(jwt, secret) }
150
+ .to raise_error JWT::VerificationError
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+
3
+ # All expected_jwt used here have been created by external tools to isolate the tests
4
+
5
+ describe 'Encoder' do
6
+ before do
7
+ Timecop.freeze(Time.at(946681200))
8
+ @payload = { hello: 'world' }
9
+ end
10
+
11
+ describe 'encoding' do
12
+ it 'encodes a JWT using HS' do
13
+ secret = 'mysecret'
14
+ expected_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0Ijo5NDY2ODEyMDB9.9OzXVFu0LK4miYiSkWrP5vhzHVtz5DHC8dWalkTah3c'
15
+ jwt = JWT.encode(@payload, secret)
16
+
17
+ expect(jwt).to eq(expected_jwt)
18
+ end
19
+
20
+ it 'encodes a JWT using RS' do
21
+ key = OpenSSL::PKey::RSA.new(<<-KEY)
22
+ -----BEGIN RSA PRIVATE KEY-----
23
+ MIIBOAIBAAJAUTQbhg/Hcgq26wY9yOWu9L6OX4fqekqoR+YhQUXaJGXhW0tNapTd
24
+ IwTAycby0cUCXoA/7IYi4SXSKMT0owQxeQIDAQABAkBI9IfF6mdGDlpIzVK1K6YE
25
+ PS+spHAFbw3BiwBVpGxYRiumQrZyjKfpPpXw1dM9Qrm0Q3hXPLEzB3Ea/aw3rAAB
26
+ AiEAoGHNoRMeBnMC7mwbi8rf8RFriWpTp91IoFzD+oPV/fECIQCBnb3Tn4Uxccmd
27
+ 2L+yoYMottUTVONq8l2YDBQlTG6ECQIgcLBHq0WjcySciqmrMS3664cx5/uti+UP
28
+ gp2rlfnMAgECIAfVedilho5Te0UQCZ4JRv0Z98zgT5JyLZf3+uu6L9/JAiBubUAY
29
+ c1CuIOv3cWDJPNMWI57s1U2WF+zbtIkc1zr5+Q==
30
+ -----END RSA PRIVATE KEY-----
31
+ KEY
32
+ jwt = JWT.encode(@payload, key, algorithm: 'RS256')
33
+ expected_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0Ijo5NDY2ODEyMDB9.A0Qiqmo02gEviZtQW8ESC94oKLXcgCSGnktyxDQZNE0AfSoMJZ9JMEWNjEXyp6k7VEm9nn9ZbEu2GdmAIJz-xw'
34
+ expect(jwt).to eq(expected_jwt)
35
+ end
36
+
37
+ it 'can use a payload with string as keys' do
38
+ payload = { 'hello' => 'world' }
39
+ secret = 'mysecret'
40
+ expected_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0Ijo5NDY2ODEyMDB9.9OzXVFu0LK4miYiSkWrP5vhzHVtz5DHC8dWalkTah3c'
41
+ jwt = JWT.encode(payload, secret)
42
+
43
+ expect(jwt).to eq(expected_jwt)
44
+ end
45
+
46
+ it 'can use old ruby hash style' do
47
+ payload = { :hello => 'world' }
48
+ secret = 'mysecret'
49
+ expected_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0Ijo5NDY2ODEyMDB9.9OzXVFu0LK4miYiSkWrP5vhzHVtz5DHC8dWalkTah3c'
50
+ jwt = JWT.encode(payload, secret)
51
+
52
+ expect(jwt).to eq(expected_jwt)
53
+ end
54
+
55
+ describe 'claims' do
56
+ it 'allows you to set an expiry time' do
57
+ secret = 'mysecret'
58
+ expected_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0Ijo5NDY2ODEyMDAsImV4cCI6OTQ2Njg3MjAwfQ.zbalHdCubQpn_U32jW1dv9L2p3vWSgf-UsKwnEiFxWo'
59
+ jwt = JWT.encode(@payload, secret, claims: { exp: 6000 })
60
+
61
+ expect(jwt).to eq(expected_jwt)
62
+ end
63
+
64
+ it 'allows you to set an audience' do
65
+ secret = 'mysecret'
66
+ expected_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0Ijo5NDY2ODEyMDAsImF1ZCI6InJ1Ynlpc3QifQ.u6DB-dpK5zxZUmgprzXtg5yY4djsLLtIv4EAgE_nWSM'
67
+ jwt = JWT.encode(@payload, secret, claims: { aud: 'rubyist' })
68
+
69
+ expect(jwt).to eq(expected_jwt)
70
+ end
71
+
72
+ it 'allows you to set an issuer' do
73
+ secret = 'mysecret'
74
+ expected_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0Ijo5NDY2ODEyMDAsImlzcyI6InRvbmkifQ.I4EoPQzt97rehCBnU8VTcDH5R2KX6b8Ta1t5bANGorc'
75
+ jwt = JWT.encode(@payload, secret, claims: { iss: 'toni' })
76
+
77
+ expect(jwt).to eq(expected_jwt)
78
+ end
79
+ end
80
+ end
81
+
82
+ describe 'exceptions' do
83
+ it 'raises NotImplementedError using a fake signature' do
84
+ secret = 'mysecret'
85
+ expect { JWT.encode(@payload, secret, algorithm: 'HS9888889') }
86
+ .to raise_error NotImplementedError
87
+ end
88
+
89
+ it 'raises an error if you dont use a private key on RS algorithms' do
90
+ secret = 'mysecret'
91
+ expect { JWT.encode(@payload, secret, algorithm: 'RS256') }
92
+ .to raise_error JWT::InvalidKeyFormatError
93
+ end
94
+ end
95
+ end
96
+
@@ -0,0 +1,5 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require 'jwt'
5
+ require 'timecop'
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jwt-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - ChupipandiCrew
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-07-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: timecop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email:
71
+ - chupi@chupipandi.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".travis.yml"
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - jwtrb.gemspec
83
+ - lib/jwt.rb
84
+ - lib/jwt/decoder.rb
85
+ - lib/jwt/encoder.rb
86
+ - lib/jwt/exceptions.rb
87
+ - lib/jwt/payload.rb
88
+ - lib/jwt/signature.rb
89
+ - lib/jwt/verificator.rb
90
+ - lib/jwt/version.rb
91
+ - spec/decoder_spec.rb
92
+ - spec/encoder_spec.rb
93
+ - spec/spec_helper.rb
94
+ homepage: http://github.com/chupipandi/jwt-rb
95
+ licenses:
96
+ - MIT
97
+ metadata: {}
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubyforge_project:
114
+ rubygems_version: 2.3.0
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: Ruby gem to encode/ decode JWT
118
+ test_files:
119
+ - spec/decoder_spec.rb
120
+ - spec/encoder_spec.rb
121
+ - spec/spec_helper.rb