jwt-rb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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