jwt_claims 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +35 -0
- data/.rspec +2 -0
- data/Gemfile +7 -0
- data/LICENSE +21 -0
- data/README.md +46 -0
- data/jwt_claims.gemspec +21 -0
- data/lib/jwt_claims/claim/aud.rb +28 -0
- data/lib/jwt_claims/claim/exp.rb +18 -0
- data/lib/jwt_claims/claim/iat.rb +19 -0
- data/lib/jwt_claims/claim/iss.rb +20 -0
- data/lib/jwt_claims/claim/jti.rb +20 -0
- data/lib/jwt_claims/claim/nbf.rb +18 -0
- data/lib/jwt_claims/claim/sub.rb +20 -0
- data/lib/jwt_claims/string_or_uri.rb +43 -0
- data/lib/jwt_claims/validation.rb +39 -0
- data/lib/jwt_claims/version.rb +3 -0
- data/lib/jwt_claims.rb +25 -0
- data/spec/jwt_claims/claim/aud_spec.rb +79 -0
- data/spec/jwt_claims/claim/exp_spec.rb +23 -0
- data/spec/jwt_claims/claim/iat_spec.rb +23 -0
- data/spec/jwt_claims/claim/iss_spec.rb +30 -0
- data/spec/jwt_claims/claim/jti_spec.rb +30 -0
- data/spec/jwt_claims/claim/nbf_spec.rb +23 -0
- data/spec/jwt_claims/claim/sub_spec.rb +30 -0
- data/spec/jwt_claims/string_or_uri_spec.rb +48 -0
- data/spec/jwt_claims/validation_spec.rb +69 -0
- data/spec/jwt_claims_spec.rb +50 -0
- data/spec/spec_helper.rb +85 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a503400ce945a893631a78c8c38af743587ce810
|
4
|
+
data.tar.gz: 3c7ecfee679bd06e177ef4ca881dbb091d524547
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7e70c10bdb07d77aa1958744d53588eeb6caca3de41eb4a8959cd77d0dfe4151a7d40c393a3437cf8b9e521e04f1e082a2d2cba04a022bd9d2762f37aec30e45
|
7
|
+
data.tar.gz: b7ac29a45cfb4992b1077d076815c8ebf098a7ad37fd90e3172504f833be6693b198e3f1063fa2c5dc06184f777d6f3cb6a01c6b8303507156ab2bbc598fa10d
|
data/.gitignore
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# https://raw.githubusercontent.com/github/gitignore/master/Ruby.gitignore
|
2
|
+
|
3
|
+
*.gem
|
4
|
+
*.rbc
|
5
|
+
/.config
|
6
|
+
/coverage/
|
7
|
+
/InstalledFiles
|
8
|
+
/pkg/
|
9
|
+
/spec/reports/
|
10
|
+
/test/tmp/
|
11
|
+
/test/version_tmp/
|
12
|
+
/tmp/
|
13
|
+
|
14
|
+
## Documentation cache and generated files:
|
15
|
+
/.yardoc/
|
16
|
+
/_yardoc/
|
17
|
+
/doc/
|
18
|
+
/rdoc/
|
19
|
+
|
20
|
+
## Environment normalisation:
|
21
|
+
/.bundle/
|
22
|
+
/vendor/bundle
|
23
|
+
/lib/bundler/man/
|
24
|
+
|
25
|
+
# for a library or gem, you might want to ignore these files since the code is
|
26
|
+
# intended to run in multiple environments; otherwise, check them in:
|
27
|
+
Gemfile.lock
|
28
|
+
.ruby-version
|
29
|
+
.ruby-gemset
|
30
|
+
|
31
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
32
|
+
.rvmrc
|
33
|
+
|
34
|
+
# custom
|
35
|
+
/spec/examples.txt
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Gary Fleshman
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# JWT Claims
|
2
|
+
|
3
|
+
## Verification of a JWT (JSON Web Token) Claims Set for Ruby
|
4
|
+
|
5
|
+
### Description
|
6
|
+
|
7
|
+
A Ruby implementation of the JSON Web Token (JWT) registered claims, [RFC 7519][rfc7519]
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
gem install jwt_claims
|
11
|
+
|
12
|
+
## Usage
|
13
|
+
|
14
|
+
### JwtClaims.verify(jwt, options)
|
15
|
+
|
16
|
+
Returns a hash, either:
|
17
|
+
* \{:ok, claims\}, a JWT claims set map, if the JWT Message Authentication Code (MAC), or signature, is verified and the registered claims are also verified
|
18
|
+
* \{:error, [rejected_claims]\}, a list of any registered claims that fail validation, if the JWT MAC is verified
|
19
|
+
* \{:error, 'invalid JWT'\} if the JWT MAC is not verified
|
20
|
+
* \{:error, 'invalid input'\} otherwise
|
21
|
+
|
22
|
+
`jwt` (required) is a JSON web token string
|
23
|
+
|
24
|
+
`options` (required) map
|
25
|
+
|
26
|
+
* **alg** (optional, default: `'HS256'`)
|
27
|
+
* **key** (required unless alg is 'none')
|
28
|
+
|
29
|
+
Please refer to the [JSON Web Token][json_web_token] gem for additional guidance regarding JWT options
|
30
|
+
|
31
|
+
Example
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
|
35
|
+
secure_jwt_example = 'eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt.cGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk'
|
36
|
+
|
37
|
+
# verify with default algorithm, HMAC SHA256
|
38
|
+
{:ok, verified_claims} = JwtClaims.verify(secure_jwt_example, key: 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C')
|
39
|
+
|
40
|
+
```
|
41
|
+
|
42
|
+
### Supported Ruby versions
|
43
|
+
Ruby 2.0.0 and up
|
44
|
+
|
45
|
+
[rfc7519]: http://tools.ietf.org/html/rfc7519
|
46
|
+
[json_web_token]: https://github.com/garyf/json_web_token
|
data/jwt_claims.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require 'jwt_claims/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.author = 'Gary Fleshman'
|
7
|
+
s.email = 'gfleshman@newforge-tech.com'
|
8
|
+
s.files = `git ls-files`.split("\n")
|
9
|
+
s.homepage = 'https://github.com/garyf/jwt_claims'
|
10
|
+
s.name = 'jwt_claims'
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.summary = 'JSON Web Token (JWT) Claims for Ruby'
|
13
|
+
s.version = JwtClaims::VERSION
|
14
|
+
# recommended
|
15
|
+
s.license = 'MIT'
|
16
|
+
# optional
|
17
|
+
s.add_runtime_dependency('json_web_token', '~> 0.3')
|
18
|
+
s.add_development_dependency('rspec', '~> 3.3')
|
19
|
+
s.description = 'Modular implementation of JSON Web Token (JWT) Claims'
|
20
|
+
s.required_ruby_version = '>= 2.0.0'
|
21
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'jwt_claims/string_or_uri'
|
2
|
+
|
3
|
+
module JwtClaims
|
4
|
+
module Claim
|
5
|
+
# Audience
|
6
|
+
# @see https://tools.ietf.org/html/rfc7519#section-4.1.3
|
7
|
+
module Aud
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
# @param aud [Array, String] the intended recipients of the JWT
|
12
|
+
# @param options [Hash] (key aud:) expected audience (or recipient) to match with claim
|
13
|
+
# @return [true, false] whether to reject the claim
|
14
|
+
def reject?(aud, options = {})
|
15
|
+
audience = aud.is_a?(Array) ? aud : [aud]
|
16
|
+
expected_recipient = options.fetch(:aud, nil)
|
17
|
+
!present_and_member?(audience, expected_recipient)
|
18
|
+
end
|
19
|
+
|
20
|
+
def present_and_member?(collection, value)
|
21
|
+
StringOrUri.present?(value) &&
|
22
|
+
collection.include?(value)
|
23
|
+
end
|
24
|
+
|
25
|
+
private_class_method :present_and_member?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module JwtClaims
|
2
|
+
module Claim
|
3
|
+
# Expiration time
|
4
|
+
# @see https://tools.ietf.org/html/rfc7519#section-4.1.4
|
5
|
+
module Exp
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# @param numeric_date [Numeric] the number of seconds from 1970-01-01T00:00:00Z UTC
|
10
|
+
# until the specified UTC date/time; non-integer values may be used
|
11
|
+
# @return [true, false] whether to reject the claim
|
12
|
+
def reject?(numeric_date, options = {})
|
13
|
+
return true unless numeric_date.is_a?(Numeric)
|
14
|
+
numeric_date <= Time.now.to_i
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module JwtClaims
|
2
|
+
module Claim
|
3
|
+
# Issued at
|
4
|
+
# @see https://tools.ietf.org/html/rfc7519#section-4.1.6
|
5
|
+
module Iat
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# @param numeric_date [Numeric] the number of seconds from 1970-01-01T00:00:00Z UTC
|
10
|
+
# until the specified UTC date/time, and non-integer values may be used
|
11
|
+
# @param options [Hash] optional, ignored
|
12
|
+
# @return [true, false] whether to reject the claim
|
13
|
+
def reject?(numeric_date, _options = {})
|
14
|
+
return true unless numeric_date.is_a?(Numeric)
|
15
|
+
numeric_date >= Time.now.to_i
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'jwt_claims/string_or_uri'
|
2
|
+
|
3
|
+
module JwtClaims
|
4
|
+
module Claim
|
5
|
+
# Issuer
|
6
|
+
# @see https://tools.ietf.org/html/rfc7519#section-4.1.1
|
7
|
+
module Iss
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
# @param iss [String] the principal that issued the JWT
|
12
|
+
# @param options [Hash] (key iss:) expected issuer to match with claim
|
13
|
+
# @return [true, false] whether to reject the claim
|
14
|
+
def reject?(iss, options = {})
|
15
|
+
expected_issuer = options.fetch(:iss, nil)
|
16
|
+
!StringOrUri.present_and_equal?(iss, expected_issuer)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'jwt_claims/string_or_uri'
|
2
|
+
|
3
|
+
module JwtClaims
|
4
|
+
module Claim
|
5
|
+
# JWT ID
|
6
|
+
# @see https://tools.ietf.org/html/rfc7519#section-4.1.7
|
7
|
+
module Jti
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
# @param jti [String] a unique identifier for the JWT
|
12
|
+
# @param options [Hash] (key jti:) expected JWT ID to match with claim
|
13
|
+
# @return [true, false] whether to reject the claim
|
14
|
+
def reject?(jti, options = {})
|
15
|
+
expected_jti = options.fetch(:jti, nil)
|
16
|
+
!StringOrUri.present_and_equal?(jti, expected_jti)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module JwtClaims
|
2
|
+
module Claim
|
3
|
+
# Not before
|
4
|
+
# @see https://tools.ietf.org/html/rfc7519#section-4.1.5
|
5
|
+
module Nbf
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# @param numeric_date [Numeric] the number of seconds from 1970-01-01T00:00:00Z UTC
|
10
|
+
# until the specified UTC date/time; non-integer values may be used
|
11
|
+
# @return [true, false] whether to reject the claim
|
12
|
+
def reject?(numeric_date, options = {})
|
13
|
+
return true unless numeric_date.is_a?(Numeric)
|
14
|
+
numeric_date > Time.now.to_i
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'jwt_claims/string_or_uri'
|
2
|
+
|
3
|
+
module JwtClaims
|
4
|
+
module Claim
|
5
|
+
# Subject
|
6
|
+
# @see https://tools.ietf.org/html/rfc7519#section-4.1.2
|
7
|
+
module Sub
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
# @param sub [String] the principal that is the subject of the JWT
|
12
|
+
# @param options [Hash] (key sub:) expected subject to match with claim
|
13
|
+
# @return [true, false] whether to reject the claim
|
14
|
+
def reject?(sub, options = {})
|
15
|
+
expected_subject = options.fetch(:sub, nil)
|
16
|
+
!StringOrUri.present_and_equal?(sub, expected_subject)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module JwtClaims
|
2
|
+
# Validation helpers
|
3
|
+
module StringOrUri
|
4
|
+
|
5
|
+
BLANK_STRING_RE = /\A[[:space:]]*\z/
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# A predicate that compares two strings for equality
|
10
|
+
#
|
11
|
+
# @param a [String]
|
12
|
+
# @param b [String]
|
13
|
+
# @return [true, false]
|
14
|
+
def present_and_equal?(a, b)
|
15
|
+
present?(a) &&
|
16
|
+
present?(b) &&
|
17
|
+
a == b
|
18
|
+
end
|
19
|
+
|
20
|
+
# A string is present if it is not blank
|
21
|
+
#
|
22
|
+
# @param a [String]
|
23
|
+
# @return [true, false]
|
24
|
+
def present?(a)
|
25
|
+
!blank?(a)
|
26
|
+
end
|
27
|
+
|
28
|
+
# A string is blank if it is empty or contains whitespaces only
|
29
|
+
#
|
30
|
+
# blank?('') # => true
|
31
|
+
# blank?(' ') # => true
|
32
|
+
# blank?("\t\n\r") # => true
|
33
|
+
# blank?('foo ') # => false
|
34
|
+
#
|
35
|
+
# @param a [String]
|
36
|
+
# @return [true, false]
|
37
|
+
# @see cf. rails activesupport/lib/active_support/core_ext/object/blank.rb
|
38
|
+
def blank?(a)
|
39
|
+
return true unless a
|
40
|
+
BLANK_STRING_RE === a
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module JwtClaims
|
2
|
+
# Validate registered claims
|
3
|
+
# @see http://tools.ietf.org/html/rfc7519#section-4.1
|
4
|
+
module Validation
|
5
|
+
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# @param claims [Hash] JWT claims
|
9
|
+
# @param options [Hash] expected values for certain claims
|
10
|
+
# @return [Array] symbols of the registered claims that fail validation
|
11
|
+
def rejected(claims, options = {})
|
12
|
+
claims.each_with_object([]) do |claim, memo|
|
13
|
+
sym = reject(*claim, options)
|
14
|
+
memo << sym if sym
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def reject(key, val, options)
|
19
|
+
return unless reg_claim = registered_claim(key)
|
20
|
+
key if reg_claim.reject?(val, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def registered_claim(sym)
|
24
|
+
case sym
|
25
|
+
when :aud then Claim::Aud
|
26
|
+
when :exp then Claim::Exp
|
27
|
+
when :iat then Claim::Iat
|
28
|
+
when :iss then Claim::Iss
|
29
|
+
when :jti then Claim::Jti
|
30
|
+
when :nbf then Claim::Nbf
|
31
|
+
when :sub then Claim::Sub
|
32
|
+
else nil # custom claim
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private_class_method :reject,
|
37
|
+
:registered_claim
|
38
|
+
end
|
39
|
+
end
|
data/lib/jwt_claims.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'json_web_token'
|
2
|
+
require 'jwt_claims/validation'
|
3
|
+
|
4
|
+
module JwtClaims
|
5
|
+
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# @param jwt [String] JSON web token
|
9
|
+
# @param options [Hash] expected values for certain claims;
|
10
|
+
# optional keys include: :aud, :iss, :jti, :sub
|
11
|
+
# @return [Hash] { ok: { the jwt claims set hash } }, or { error: [symbols of all rejected claims] }
|
12
|
+
def verify(jwt, options)
|
13
|
+
hsh = JsonWebToken.verify(jwt, options)
|
14
|
+
return {error: 'invalid JWT'} if hsh[:error]
|
15
|
+
claims = hsh[:ok]
|
16
|
+
return {error: 'invalid input'} unless claims
|
17
|
+
verified(claims, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def verified(claims, options)
|
21
|
+
rejected_claims = Validation.rejected(claims, options)
|
22
|
+
return {ok: claims} if rejected_claims.empty?
|
23
|
+
{error: rejected_claims}
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'jwt_claims/claim/aud'
|
2
|
+
|
3
|
+
module JwtClaims
|
4
|
+
module Claim
|
5
|
+
describe Aud do
|
6
|
+
context '#reject?' do
|
7
|
+
context 'w :aud claim an array' do
|
8
|
+
let(:recipient) { 'recipient' }
|
9
|
+
let(:uri) { 'http://www.example.com' }
|
10
|
+
let(:aud) { [uri, recipient] }
|
11
|
+
describe 'w match' do
|
12
|
+
it 'string returns false' do
|
13
|
+
expect(Aud.reject? aud, {aud: recipient}).to be false
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'uri returns false' do
|
17
|
+
expect(Aud.reject? aud, {aud: uri}).to be false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'w/o match returns true' do
|
22
|
+
expect(Aud.reject? aud, {aud: 'not recipient'}).to be true
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'w/o options[:aud] returns true' do
|
26
|
+
expect(Aud.reject? aud, {iss: 'recipient'}).to be true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'w :aud claim a string' do
|
31
|
+
let(:recipient) { 'recipient' }
|
32
|
+
let(:aud) { recipient }
|
33
|
+
it 'w match returns false' do
|
34
|
+
expect(Aud.reject? aud, {aud: recipient}).to be false
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'w/o match returns true' do
|
38
|
+
expect(Aud.reject? aud, {aud: 'not recipient'}).to be true
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'w/o options[:aud] returns true' do
|
42
|
+
expect(Aud.reject? aud, {iss: 'recipient'}).to be true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
shared_examples_for 'a blank :aud claim' do
|
47
|
+
let(:options) { {aud: 'recipient'} }
|
48
|
+
it 'returns true' do
|
49
|
+
expect(Aud.reject? aud, options).to be true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'w :aud an empty array' do
|
54
|
+
let(:aud) { [] }
|
55
|
+
it_behaves_like 'a blank :aud claim'
|
56
|
+
end
|
57
|
+
|
58
|
+
describe 'w :aud an array w an empty string' do
|
59
|
+
let(:aud) { [''] }
|
60
|
+
it_behaves_like 'a blank :aud claim'
|
61
|
+
end
|
62
|
+
|
63
|
+
describe 'w :aud an empty string' do
|
64
|
+
let(:aud) { '' }
|
65
|
+
it_behaves_like 'a blank :aud claim'
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'w options[:aud] an empty string' do
|
69
|
+
let(:aud) { 'recipient' }
|
70
|
+
let(:options) { {aud: ''} }
|
71
|
+
it 'returns truthy' do
|
72
|
+
expect(Aud.reject? aud, options).to be true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'jwt_claims/claim/exp'
|
2
|
+
|
3
|
+
module JwtClaims
|
4
|
+
module Claim
|
5
|
+
describe Exp do
|
6
|
+
let(:after_now) { Time.now.to_i + 1 }
|
7
|
+
describe '#reject?' do
|
8
|
+
it 'w numeric_date after now returns false' do
|
9
|
+
expect(Exp.reject? after_now).to be false
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'w numeric_date now returns true' do
|
13
|
+
expect(Exp.reject? Time.now.to_i).to be true
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'w/o numeric numeric_date returns true' do
|
17
|
+
expect(Exp.reject? after_now.to_s).to be true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'jwt_claims/claim/iat'
|
2
|
+
|
3
|
+
module JwtClaims
|
4
|
+
module Claim
|
5
|
+
describe Iat do
|
6
|
+
let(:before_now) { Time.now.to_i - 1 }
|
7
|
+
describe '#reject?' do
|
8
|
+
it 'w numeric_date now returns false' do
|
9
|
+
expect(Iat.reject? before_now).to be false
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'w numeric_date now returns true' do
|
13
|
+
expect(Iat.reject? Time.now.to_i).to be true
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'w/o numeric claimed_time returns true' do
|
17
|
+
expect(Iat.reject? before_now.to_s).to be true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'jwt_claims/claim/iss'
|
2
|
+
|
3
|
+
module JwtClaims
|
4
|
+
module Claim
|
5
|
+
describe Iss do
|
6
|
+
context '#reject?' do
|
7
|
+
let(:issuer) { 'issuer' }
|
8
|
+
describe 'w an :iss claim' do
|
9
|
+
let(:iss) { issuer }
|
10
|
+
it 'w match returns false' do
|
11
|
+
expect(Iss.reject? iss, {iss: issuer}).to be false
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'w/o match returns true' do
|
15
|
+
expect(Iss.reject? iss, {iss: 'not issuer'}).to be true
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'w/o options[:iss] returns true' do
|
19
|
+
expect(Iss.reject? iss, {aud: 'issuer'}).to be true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'w a blank :iss claim returns true' do
|
24
|
+
expect(Iss.reject? '', {iss: issuer}).to be true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'jwt_claims/claim/jti'
|
2
|
+
|
3
|
+
module JwtClaims
|
4
|
+
module Claim
|
5
|
+
describe Jti do
|
6
|
+
context '#reject?' do
|
7
|
+
let(:jwt_id) { 'jwt_id' }
|
8
|
+
describe 'w a :jti claim' do
|
9
|
+
let(:jti) { jwt_id }
|
10
|
+
it 'w match returns false' do
|
11
|
+
expect(Jti.reject? jti, {jti: jwt_id}).to be false
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'w/o match returns true' do
|
15
|
+
expect(Jti.reject? jti, {jti: 'not jwt_id'}).to be true
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'w/o options[:jti] returns true' do
|
19
|
+
expect(Jti.reject? jti, {aud: jwt_id}).to be true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'w a blank :jti claim returns true' do
|
24
|
+
expect(Jti.reject? '', {jti: jwt_id}).to be true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'jwt_claims/claim/nbf'
|
2
|
+
|
3
|
+
module JwtClaims
|
4
|
+
module Claim
|
5
|
+
describe Nbf do
|
6
|
+
let(:after_now) { Time.now.to_i + 1 }
|
7
|
+
describe '#reject?' do
|
8
|
+
it 'w numeric_date now returns false' do
|
9
|
+
expect(Nbf.reject? Time.now.to_i).to be false
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'w numeric_date after now returns true' do
|
13
|
+
expect(Nbf.reject? after_now).to be true
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'w/o numeric claimed_time returns true' do
|
17
|
+
expect(Nbf.reject? after_now.to_s).to be true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'jwt_claims/claim/sub'
|
2
|
+
|
3
|
+
module JwtClaims
|
4
|
+
module Claim
|
5
|
+
describe Sub do
|
6
|
+
context '#reject?' do
|
7
|
+
let(:subject) { 'subject' }
|
8
|
+
describe 'w a :sub claim' do
|
9
|
+
let(:sub) { subject }
|
10
|
+
it 'w match returns false' do
|
11
|
+
expect(Sub.reject? sub, {sub: subject}).to be false
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'w/o match returns true' do
|
15
|
+
expect(Sub.reject? sub, {sub: 'not subject'}).to be true
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'w/o options[:sub] returns true' do
|
19
|
+
expect(Sub.reject? sub, {aud: subject}).to be true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'w a blank :sub claim returns true' do
|
24
|
+
expect(Sub.reject? '', {sub: subject}).to be true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'jwt_claims/string_or_uri'
|
2
|
+
|
3
|
+
module JwtClaims
|
4
|
+
describe StringOrUri do
|
5
|
+
context '#present_and_equal?' do
|
6
|
+
it 'w equality, returns true' do
|
7
|
+
expect(StringOrUri.present_and_equal? 'foo', 'foo').to be true
|
8
|
+
end
|
9
|
+
|
10
|
+
shared_examples_for 'a blank value or not equal' do
|
11
|
+
it 'returns false' do
|
12
|
+
expect(StringOrUri.present_and_equal? a, b).to be false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'w 1st argument blank' do
|
17
|
+
let(:a) { '' }
|
18
|
+
let(:b) { 'bar' }
|
19
|
+
it_behaves_like 'a blank value or not equal'
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'w 2nd argument blank' do
|
23
|
+
let(:a) { 'foo' }
|
24
|
+
let(:b) { '' }
|
25
|
+
it_behaves_like 'a blank value or not equal'
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'w/o equality' do
|
29
|
+
let(:a) { 'foo' }
|
30
|
+
let(:b) { 'bar' }
|
31
|
+
it_behaves_like 'a blank value or not equal'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#blank?' do
|
36
|
+
it 'classifies nil, empty, or exclusively whitespace strings' do
|
37
|
+
expect(StringOrUri.blank? nil).to be true
|
38
|
+
expect(StringOrUri.blank? '').to be true
|
39
|
+
expect(StringOrUri.blank? ' ').to be true
|
40
|
+
expect(StringOrUri.blank? "\t\n\r").to be true
|
41
|
+
|
42
|
+
expect(StringOrUri.blank? 'foo').to be false
|
43
|
+
expect(StringOrUri.blank? 'bar ').to be false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'jwt_claims/validation'
|
2
|
+
|
3
|
+
module JwtClaims
|
4
|
+
describe Validation do
|
5
|
+
context 'example data' do
|
6
|
+
let(:uri) { 'http://www.example.com' }
|
7
|
+
let(:recipient) { 'recipient' }
|
8
|
+
let(:aud) { [uri, recipient] }
|
9
|
+
|
10
|
+
let(:after_now) { Time.now.to_i + 1 }
|
11
|
+
let(:before_now) { Time.now.to_i - 1 }
|
12
|
+
|
13
|
+
let(:issuer) { 'issuer' }
|
14
|
+
let(:jwt_id) { 'jwt_id' }
|
15
|
+
let(:subject) { 'subject' }
|
16
|
+
|
17
|
+
let(:default_options) do
|
18
|
+
{
|
19
|
+
aud: uri,
|
20
|
+
iss: issuer,
|
21
|
+
jti: jwt_id,
|
22
|
+
sub: subject
|
23
|
+
}
|
24
|
+
end
|
25
|
+
let(:default_claims) do
|
26
|
+
{
|
27
|
+
aud: [uri, recipient],
|
28
|
+
exp: after_now,
|
29
|
+
iat: before_now,
|
30
|
+
iss: issuer,
|
31
|
+
jti: jwt_id,
|
32
|
+
nbf: before_now,
|
33
|
+
sub: subject
|
34
|
+
}
|
35
|
+
end
|
36
|
+
context '#rejected' do
|
37
|
+
it 'w valid claims, returns empty array' do
|
38
|
+
expect(Validation.rejected default_claims, default_options).to eql []
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'w all registered claims invalid' do
|
42
|
+
let(:invalid_claims) do
|
43
|
+
{
|
44
|
+
aud: ['http://www.other.com', 'other recipient'],
|
45
|
+
exp: before_now,
|
46
|
+
iat: after_now,
|
47
|
+
iss: 'other issuer',
|
48
|
+
jti: 'other jwt_id',
|
49
|
+
nbf: after_now,
|
50
|
+
sub: 'other subject'
|
51
|
+
}
|
52
|
+
end
|
53
|
+
it 'returns array of failed claims' do
|
54
|
+
expect(Validation.rejected invalid_claims, default_options)
|
55
|
+
.to include(
|
56
|
+
:aud,
|
57
|
+
:exp,
|
58
|
+
:iat,
|
59
|
+
:iss,
|
60
|
+
:jti,
|
61
|
+
:nbf,
|
62
|
+
:sub
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'jwt_claims'
|
2
|
+
|
3
|
+
describe JwtClaims do
|
4
|
+
let(:after_now) { Time.now.to_i + 1 }
|
5
|
+
let(:before_now) { Time.now.to_i - 1 }
|
6
|
+
context '#verify' do
|
7
|
+
describe 'JsonWebToken integration w :exp claim before now' do
|
8
|
+
let(:jwt_w_exp_before_now_w_iss_joe) do
|
9
|
+
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiZXhwIjoxMzAwODE5MzgwfQ.Ktfu3EdLz0SpuTIMpMoRZMtZsCATWJHeDEBGrsZE6LI"
|
10
|
+
end
|
11
|
+
let(:hs256_key) { "gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C" }
|
12
|
+
it 'w :iss claim matching' do
|
13
|
+
options = {iss: 'joe', key: hs256_key}
|
14
|
+
rejected_claims = JwtClaims.verify(jwt_w_exp_before_now_w_iss_joe, options)[:error]
|
15
|
+
expect(rejected_claims.length).to eql 1
|
16
|
+
expect(rejected_claims).to include(:exp)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'w/o :iss claim matching' do
|
20
|
+
options = {iss: 'mary', key: hs256_key}
|
21
|
+
rejected_claims = JwtClaims.verify(jwt_w_exp_before_now_w_iss_joe, options)[:error]
|
22
|
+
expect(rejected_claims.length).to eql 2
|
23
|
+
expect(rejected_claims).to include(:exp, :iss)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'w invalid JWT' do
|
28
|
+
let(:invalid_jwt) do
|
29
|
+
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiZXhwIjoxMzAwODE5MzgwfQ.Ktfu3EdLz0SpuTIMpMoRZMtZsCATWJHeDEBGrsZE6LX"
|
30
|
+
end
|
31
|
+
it 'returns error' do
|
32
|
+
options = {exp: after_now}
|
33
|
+
error = JwtClaims.verify(invalid_jwt, options)[:error]
|
34
|
+
expect(error).to eql 'invalid JWT'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#verified' do
|
40
|
+
it 'w rejected_claims' do
|
41
|
+
claims = {exp: before_now}
|
42
|
+
expect(JwtClaims.verified claims, {}).to include(error: [:exp])
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'w/o rejected_claims' do
|
46
|
+
claims = {exp: after_now}
|
47
|
+
expect(JwtClaims.verified claims, {}).to include(ok: claims)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start
|
3
|
+
|
4
|
+
# Conventionally, all specs live under a `spec` directory, which RSpec adds to
|
5
|
+
# the `$LOAD_PATH`. The generated `.rspec` file contains `--require spec_helper`
|
6
|
+
# which will cause this file to always be loaded, without a need to explicitly
|
7
|
+
# require it in any files.
|
8
|
+
#
|
9
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
10
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
11
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
12
|
+
# individual file that may not need all of that loaded. Instead, consider
|
13
|
+
# making a separate helper file that requires the additional dependencies and
|
14
|
+
# performs the additional setup, and require it from the spec files that
|
15
|
+
# actually need it.
|
16
|
+
#
|
17
|
+
# The `.rspec` file also contains a few flags that are not defaults but that
|
18
|
+
# users commonly want.
|
19
|
+
#
|
20
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.expect_with :rspec do |expectations|
|
23
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
24
|
+
# and `failure_message` of custom matchers include text for helper methods
|
25
|
+
# defined using `chain`, e.g.:
|
26
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
27
|
+
# # => "be bigger than 2 and smaller than 4"
|
28
|
+
# ...rather than:
|
29
|
+
# # => "be bigger than 2"
|
30
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
31
|
+
end
|
32
|
+
|
33
|
+
config.mock_with :rspec do |mocks|
|
34
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
35
|
+
# a real object. This is generally recommended, and will default to
|
36
|
+
# `true` in RSpec 4.
|
37
|
+
mocks.verify_partial_doubles = true
|
38
|
+
end
|
39
|
+
|
40
|
+
# The settings below are suggested to provide a good initial experience
|
41
|
+
# with RSpec, but feel free to customize to your heart's content.
|
42
|
+
|
43
|
+
# These two settings work together to allow you to limit a spec run to
|
44
|
+
# individual examples or groups you care about by tagging them with `:focus`
|
45
|
+
# metadata. When nothing is tagged with `:focus`, all examples get run.
|
46
|
+
config.filter_run :focus
|
47
|
+
config.run_all_when_everything_filtered = true
|
48
|
+
|
49
|
+
# Allows RSpec to persist some state between runs in order to support the
|
50
|
+
# `--only-failures` and `--next-failure` CLI options. We recommend you
|
51
|
+
# configure your source control system to ignore this file.
|
52
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
53
|
+
|
54
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
55
|
+
# recommended. For more details, see:
|
56
|
+
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
|
57
|
+
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
58
|
+
# - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
|
59
|
+
# config.disable_monkey_patching!
|
60
|
+
|
61
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
62
|
+
# file, and it's useful to allow more verbose output when running an
|
63
|
+
# individual spec file.
|
64
|
+
if config.files_to_run.one?
|
65
|
+
# Use the documentation formatter for detailed output, unless a formatter
|
66
|
+
# has already been configured (e.g. via a command-line flag)
|
67
|
+
config.default_formatter = 'doc'
|
68
|
+
end
|
69
|
+
|
70
|
+
# Print the 10 slowest examples and example groups at the end of the spec
|
71
|
+
# run, to help surface which specs are running particularly slowly.
|
72
|
+
# config.profile_examples = 10
|
73
|
+
|
74
|
+
# Run specs in random order to surface order dependencies. If you find an
|
75
|
+
# order dependency and want to debug it, you can fix the order by providing
|
76
|
+
# the seed, which is printed after each run.
|
77
|
+
# --seed 1234
|
78
|
+
config.order = :random
|
79
|
+
|
80
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
81
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
82
|
+
# test failures related to randomization by passing the same `--seed` value
|
83
|
+
# as the one that triggered the failure.
|
84
|
+
Kernel.srand config.seed
|
85
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jwt_claims
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gary Fleshman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: json_web_token
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.3'
|
41
|
+
description: Modular implementation of JSON Web Token (JWT) Claims
|
42
|
+
email: gfleshman@newforge-tech.com
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- ".gitignore"
|
48
|
+
- ".rspec"
|
49
|
+
- Gemfile
|
50
|
+
- LICENSE
|
51
|
+
- README.md
|
52
|
+
- jwt_claims.gemspec
|
53
|
+
- lib/jwt_claims.rb
|
54
|
+
- lib/jwt_claims/claim/aud.rb
|
55
|
+
- lib/jwt_claims/claim/exp.rb
|
56
|
+
- lib/jwt_claims/claim/iat.rb
|
57
|
+
- lib/jwt_claims/claim/iss.rb
|
58
|
+
- lib/jwt_claims/claim/jti.rb
|
59
|
+
- lib/jwt_claims/claim/nbf.rb
|
60
|
+
- lib/jwt_claims/claim/sub.rb
|
61
|
+
- lib/jwt_claims/string_or_uri.rb
|
62
|
+
- lib/jwt_claims/validation.rb
|
63
|
+
- lib/jwt_claims/version.rb
|
64
|
+
- spec/jwt_claims/claim/aud_spec.rb
|
65
|
+
- spec/jwt_claims/claim/exp_spec.rb
|
66
|
+
- spec/jwt_claims/claim/iat_spec.rb
|
67
|
+
- spec/jwt_claims/claim/iss_spec.rb
|
68
|
+
- spec/jwt_claims/claim/jti_spec.rb
|
69
|
+
- spec/jwt_claims/claim/nbf_spec.rb
|
70
|
+
- spec/jwt_claims/claim/sub_spec.rb
|
71
|
+
- spec/jwt_claims/string_or_uri_spec.rb
|
72
|
+
- spec/jwt_claims/validation_spec.rb
|
73
|
+
- spec/jwt_claims_spec.rb
|
74
|
+
- spec/spec_helper.rb
|
75
|
+
homepage: https://github.com/garyf/jwt_claims
|
76
|
+
licenses:
|
77
|
+
- MIT
|
78
|
+
metadata: {}
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: 2.0.0
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 2.4.8
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: JSON Web Token (JWT) Claims for Ruby
|
99
|
+
test_files: []
|
100
|
+
has_rdoc:
|