jwt_signed_request 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +178 -0
- data/lib/jwt_signed_request/claims.rb +81 -0
- data/lib/jwt_signed_request/headers.rb +68 -0
- data/lib/jwt_signed_request/middlewares/faraday.rb +40 -0
- data/lib/jwt_signed_request/middlewares/rack.rb +34 -0
- data/lib/jwt_signed_request/version.rb +3 -0
- data/lib/jwt_signed_request.rb +68 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 753794ac95f697ec022de56fe9d4db85265e4920
|
4
|
+
data.tar.gz: 16109643b05330e94cd586f246e57039892fa2ee
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 85f8a16181085937c113444c211d7e0eba0ecfe865d6e829576589713825df3263f392f44a4edb08ccf2ad3986bcf702964622ed9f8e69dc28f56d85a0f83283
|
7
|
+
data.tar.gz: dbbd9e2db0bfe7c0ca0c53f233af500f02aa878dfdf52edefe8408b7629b18d11da4a05016873e6361a96fb1626175e187410523e0954c9bcd558a3197bad9ff
|
data/README.md
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
# JWT Signed Request
|
2
|
+
|
3
|
+
Request signing and verification for Internal APIs using JWT.
|
4
|
+
|
5
|
+
## Getting Started
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'jwt_signed_request'
|
11
|
+
```
|
12
|
+
|
13
|
+
then run:
|
14
|
+
|
15
|
+
```sh
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
## Generating EC Keys
|
20
|
+
|
21
|
+
We should be using a public key encryption alogorithm such as **ES256**. To generate your public/private key pair using **ES256** run:
|
22
|
+
|
23
|
+
```sh
|
24
|
+
$ openssl ecparam -genkey -name prime256v1 -noout -out myprivatekey.pem
|
25
|
+
$ openssl ec -in myprivatekey.pem -pubout -out mypubkey.pem
|
26
|
+
```
|
27
|
+
|
28
|
+
Store and encrypt these in your application secrets.
|
29
|
+
|
30
|
+
## Signing Requests
|
31
|
+
|
32
|
+
If you are using an asymmetrical encryption algorithm such as ES256 you will sign your requests using the private key.
|
33
|
+
|
34
|
+
### Using net/http
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
require 'net/http'
|
38
|
+
require 'uri'
|
39
|
+
require 'openssl'
|
40
|
+
require 'jwt_signed_request'
|
41
|
+
|
42
|
+
private_key = """
|
43
|
+
-----BEGIN EC PRIVATE KEY-----
|
44
|
+
MHcCAQEEIBOQ3YIILYMV1glTKbF9oeZWzHe3SNQjAx4IbPIxNygQoAoGCCqGSM49
|
45
|
+
AwEHoUQDQgAEuOC3ufTTnW0hVmCPNERb4LxaDE/OexDdlmXEjHYaixzYIduluGXd
|
46
|
+
3cjg4H2gjqsY/NCpJ9nM8/AAINSrq+qPuA==
|
47
|
+
-----END EC PRIVATE KEY-----
|
48
|
+
"""
|
49
|
+
|
50
|
+
uri = URI('http://example.com')
|
51
|
+
req = Net::HTTP::Get.new(uri)
|
52
|
+
|
53
|
+
req['Authorization'] = JWTSignedRequest.sign(
|
54
|
+
method: req.method,
|
55
|
+
path: req.path,
|
56
|
+
headers: {"Content-Type" => "application/json"},
|
57
|
+
body: "",
|
58
|
+
secret_key: OpenSSL::PKey::EC.new(private_key),
|
59
|
+
algorithm: 'ES256', # optional (default: ES256)
|
60
|
+
key_id: 'my-key-id', # optional
|
61
|
+
issuer: 'my-issuer' # optional
|
62
|
+
additional_headers_to_sign: ['X-AUTH'] # optional
|
63
|
+
)
|
64
|
+
|
65
|
+
res = Net::HTTP.start(uri.hostname, uri.port) {|http|
|
66
|
+
http.request(req)
|
67
|
+
}
|
68
|
+
```
|
69
|
+
|
70
|
+
### Using faraday
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
require 'faraday'
|
74
|
+
require 'openssl'
|
75
|
+
require 'jwt_signed_request/middlewares/faraday'
|
76
|
+
|
77
|
+
private_key = """
|
78
|
+
-----BEGIN EC PRIVATE KEY-----
|
79
|
+
MHcCAQEEIBOQ3YIILYMV1glTKbF9oeZWzHe3SNQjAx4IbPIxNygQoAoGCCqGSM49
|
80
|
+
AwEHoUQDQgAEuOC3ufTTnW0hVmCPNERb4LxaDE/OexDdlmXEjHYaixzYIduluGXd
|
81
|
+
3cjg4H2gjqsY/NCpJ9nM8/AAINSrq+qPuA==
|
82
|
+
-----END EC PRIVATE KEY-----
|
83
|
+
"""
|
84
|
+
|
85
|
+
conn = Faraday.new(url: URI.parse('http://example.com')) do |faraday|
|
86
|
+
faraday.use JWTSignedRequest::Middlewares::Faraday,
|
87
|
+
secret_key: OpenSSL::PKey::EC.new(private_key),
|
88
|
+
algorithm: 'EC256', # optional (default: ES256)
|
89
|
+
key_id: 'my-key-id', # optional
|
90
|
+
issuer: 'my-issuer', # optional
|
91
|
+
additional_headers_to_sign: ['X-AUTH'] # optional
|
92
|
+
|
93
|
+
faraday.adapter Faraday.default_adapter
|
94
|
+
end
|
95
|
+
|
96
|
+
conn.post do |req|
|
97
|
+
req.url 'http://example.com'
|
98
|
+
req.body = '{ "name": "Unagi" }'
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
## Verifying Requests
|
103
|
+
|
104
|
+
If you are using an asymmetrical encryption algorithm such as ES256 you will verify the request using the public key.
|
105
|
+
|
106
|
+
## Using Rails
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
class APIController < ApplicationController
|
110
|
+
PUBLIC_KEY = """
|
111
|
+
-----BEGIN PUBLIC KEY-----
|
112
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuOC3ufTTnW0hVmCPNERb4LxaDE/O
|
113
|
+
exDdlmXEjHYaixzYIduluGXd3cjg4H2gjqsY/NCpJ9nM8/AAINSrq+qPuA==
|
114
|
+
-----END PUBLIC KEY-----
|
115
|
+
"""
|
116
|
+
|
117
|
+
before_action :verify_request
|
118
|
+
|
119
|
+
...
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def verify_request
|
124
|
+
begin
|
125
|
+
JWTSignedRequest.verify(
|
126
|
+
request: request,
|
127
|
+
secret_key: OpenSSL::PKey::EC.new(PUBLIC_KEY)
|
128
|
+
)
|
129
|
+
|
130
|
+
rescue JWTSignedRequest::UnauthorizedRequestError => e
|
131
|
+
render :json => {}, :status => :unauthorized
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
## Using Rack Middleware
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
PUBLIC_KEY = """
|
142
|
+
-----BEGIN PUBLIC KEY-----
|
143
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuOC3ufTTnW0hVmCPNERb4LxaDE/O
|
144
|
+
exDdlmXEjHYaixzYIduluGXd3cjg4H2gjqsY/NCpJ9nM8/AAINSrq+qPuA==
|
145
|
+
-----END PUBLIC KEY-----
|
146
|
+
"""
|
147
|
+
|
148
|
+
class Server < Sinatra::Base
|
149
|
+
use JWTSignedRequest::Middlewares::Rack
|
150
|
+
secret_key: OpenSSL::PKey::EC.new(PUBLIC_KEY)
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
## Maintainers
|
155
|
+
- [Toan Nguyen](https://github.com/yoshdog)
|
156
|
+
|
157
|
+
## License
|
158
|
+
|
159
|
+
`JWTSignedRequest` uses MIT license. See
|
160
|
+
[`LICENSE.txt`](https://github.com/envato/jwt_signed_request/blob/master/LICENSE.txt) for
|
161
|
+
details.
|
162
|
+
|
163
|
+
## Code of conduct
|
164
|
+
|
165
|
+
We welcome contribution from everyone. Read more about it in
|
166
|
+
[`CODE_OF_CONDUCT.md`](https://github.com/envato/jwt_signed_request/blob/master/CODE_OF_CONDUCT.md)
|
167
|
+
|
168
|
+
## Contributing
|
169
|
+
|
170
|
+
For bug fixes, documentation changes, and small features:
|
171
|
+
|
172
|
+
1. Fork it ( https://github.com/[my-github-username]/jwt_signed_request/fork )
|
173
|
+
2. Create your feature branch (git checkout -b my-new-feature)
|
174
|
+
3. Commit your changes (git commit -am 'Add some feature')
|
175
|
+
4. Push to the branch (git push origin my-new-feature)
|
176
|
+
5. Create a new Pull Request
|
177
|
+
|
178
|
+
For larger new features: Do everything as above, but first also make contact with the project maintainers to be sure your change fits with the project direction and you won't be wasting effort going in the wrong direction
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'json'
|
3
|
+
require 'rack/utils'
|
4
|
+
|
5
|
+
module JWTSignedRequest
|
6
|
+
class Claims
|
7
|
+
EMPTY_HEADERS = [].freeze
|
8
|
+
|
9
|
+
def self.generate(args)
|
10
|
+
new(**args).generate
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(method:, path:, headers:, body:, additional_headers_to_sign: EMPTY_HEADERS, timeout: DEFAULT_TIMEOUT, issuer:)
|
14
|
+
@method = method
|
15
|
+
@path = path
|
16
|
+
@headers = headers
|
17
|
+
@body = body
|
18
|
+
@additional_headers_to_sign = additional_headers_to_sign
|
19
|
+
@timeout = timeout
|
20
|
+
@issuer = issuer
|
21
|
+
end
|
22
|
+
|
23
|
+
private_class_method :new
|
24
|
+
|
25
|
+
def generate
|
26
|
+
result = {
|
27
|
+
method: method,
|
28
|
+
path: path,
|
29
|
+
headers: serialized_headers,
|
30
|
+
body_sha: body_sha,
|
31
|
+
exp: (Time.now + timeout).to_i
|
32
|
+
}
|
33
|
+
result[:iss] = issuer if issuer
|
34
|
+
result
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :method, :path, :headers, :body, :additional_headers_to_sign, :timeout, :issuer
|
40
|
+
|
41
|
+
HEADERS_TO_SIGN = %w(
|
42
|
+
Content-Type
|
43
|
+
Content-Length
|
44
|
+
Date
|
45
|
+
User-Agent
|
46
|
+
).freeze
|
47
|
+
|
48
|
+
private_constant :HEADERS_TO_SIGN
|
49
|
+
|
50
|
+
DEFAULT_TIMEOUT = 30.freeze
|
51
|
+
|
52
|
+
private_constant :DEFAULT_TIMEOUT
|
53
|
+
|
54
|
+
def formatted_body
|
55
|
+
case body
|
56
|
+
when String
|
57
|
+
body
|
58
|
+
when Array, Hash
|
59
|
+
Rack::Utils.build_query(body)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def body_sha
|
64
|
+
Digest::SHA256.hexdigest(formatted_body)
|
65
|
+
end
|
66
|
+
|
67
|
+
def headers_to_sign
|
68
|
+
HEADERS_TO_SIGN + additional_headers_to_sign
|
69
|
+
end
|
70
|
+
|
71
|
+
def filtered_headers
|
72
|
+
headers.select do |header_name, _|
|
73
|
+
headers_to_sign.include?(header_name)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def serialized_headers
|
78
|
+
JSON.dump(filtered_headers)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# We need a way to pull out the headers from a RAW Rack ENV hash.
|
2
|
+
#
|
3
|
+
# We took out the bits we need to lookup the headers from:
|
4
|
+
# https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/headers.rb
|
5
|
+
#
|
6
|
+
# We didn't want to include actionpack as a dependency of the library as it brings in alot of
|
7
|
+
# other dependencies.
|
8
|
+
|
9
|
+
module JWTSignedRequest
|
10
|
+
class Headers
|
11
|
+
def self.fetch(key, request)
|
12
|
+
new(request).fetch(key)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(request)
|
16
|
+
@request = request
|
17
|
+
end
|
18
|
+
|
19
|
+
def fetch(key)
|
20
|
+
env_key = env_name(key)
|
21
|
+
request_env[env_key]
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :request
|
27
|
+
|
28
|
+
def request_env
|
29
|
+
request.env
|
30
|
+
end
|
31
|
+
|
32
|
+
CGI_VARIABLES = Set.new(%W[
|
33
|
+
AUTH_TYPE
|
34
|
+
CONTENT_LENGTH
|
35
|
+
CONTENT_TYPE
|
36
|
+
GATEWAY_INTERFACE
|
37
|
+
HTTPS
|
38
|
+
PATH_INFO
|
39
|
+
PATH_TRANSLATED
|
40
|
+
QUERY_STRING
|
41
|
+
REMOTE_ADDR
|
42
|
+
REMOTE_HOST
|
43
|
+
REMOTE_IDENT
|
44
|
+
REMOTE_USER
|
45
|
+
REQUEST_METHOD
|
46
|
+
SCRIPT_NAME
|
47
|
+
SERVER_NAME
|
48
|
+
SERVER_PORT
|
49
|
+
SERVER_PROTOCOL
|
50
|
+
SERVER_SOFTWARE
|
51
|
+
]).freeze
|
52
|
+
|
53
|
+
private_constant :CGI_VARIABLES
|
54
|
+
|
55
|
+
HTTP_HEADER = /\A[A-Za-z0-9-]+\z/
|
56
|
+
|
57
|
+
private_constant :HTTP_HEADER
|
58
|
+
|
59
|
+
def env_name(key)
|
60
|
+
key = key.to_s
|
61
|
+
if key =~ HTTP_HEADER
|
62
|
+
key = key.upcase.tr('-', '_')
|
63
|
+
key = "HTTP_" + key unless CGI_VARIABLES.include?(key)
|
64
|
+
end
|
65
|
+
key
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'jwt_signed_request'
|
3
|
+
|
4
|
+
module JWTSignedRequest
|
5
|
+
module Middlewares
|
6
|
+
class Faraday < Faraday::Middleware
|
7
|
+
def initialize(app, options)
|
8
|
+
@options = options
|
9
|
+
super(app)
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
jwt_token = ::JWTSignedRequest.sign(
|
14
|
+
method: env[:method],
|
15
|
+
path: env[:url].request_uri,
|
16
|
+
headers: env[:request_headers],
|
17
|
+
body: env.fetch(:body, ::JWTSignedRequest::EMPTY_BODY),
|
18
|
+
secret_key: options[:secret_key],
|
19
|
+
**optional_settings
|
20
|
+
)
|
21
|
+
|
22
|
+
env[:request_headers].store("Authorization", jwt_token)
|
23
|
+
app.call(env)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :app, :env, :options
|
29
|
+
|
30
|
+
def optional_settings
|
31
|
+
{
|
32
|
+
algorithm: options[:algorithm],
|
33
|
+
additional_headers_to_sign: options[:additional_headers_to_sign],
|
34
|
+
key_id: options[:key_id],
|
35
|
+
issuer: options[:issuer],
|
36
|
+
}.reject { |_, value| value.nil? }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'jwt_signed_request'
|
3
|
+
|
4
|
+
module JWTSignedRequest
|
5
|
+
module Middlewares
|
6
|
+
class Rack
|
7
|
+
UNAUTHORIZED_STATUS_CODE = 401.freeze
|
8
|
+
|
9
|
+
def initialize(app, options = {})
|
10
|
+
@app = app
|
11
|
+
@secret_key = options.fetch(:secret_key)
|
12
|
+
@algorithm = options[:algorithm]
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
begin
|
17
|
+
::JWTSignedRequest.verify(
|
18
|
+
request: ::Rack::Request.new(env),
|
19
|
+
secret_key: secret_key,
|
20
|
+
algorithm: algorithm
|
21
|
+
)
|
22
|
+
|
23
|
+
app.call(env)
|
24
|
+
rescue ::JWTSignedRequest::UnauthorizedRequestError => e
|
25
|
+
[UNAUTHORIZED_STATUS_CODE, {'Content-Type' => 'application/json'} , []]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :app, :secret_key, :algorithm
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'jwt'
|
2
|
+
require 'jwt_signed_request/claims'
|
3
|
+
require 'jwt_signed_request/headers'
|
4
|
+
|
5
|
+
module JWTSignedRequest
|
6
|
+
DEFAULT_ALGORITHM = 'ES256'.freeze
|
7
|
+
EMPTY_BODY = "".freeze
|
8
|
+
|
9
|
+
UnauthorizedRequestError = Class.new(StandardError)
|
10
|
+
MissingAuthorizationHeaderError = Class.new(UnauthorizedRequestError)
|
11
|
+
JWTDecodeError = Class.new(UnauthorizedRequestError)
|
12
|
+
RequestVerificationFailedError = Class.new(UnauthorizedRequestError)
|
13
|
+
|
14
|
+
def self.sign(method:, path:, body: EMPTY_BODY, headers:, secret_key:, algorithm: DEFAULT_ALGORITHM, key_id: nil, issuer: nil, additional_headers_to_sign: Claims::EMPTY_HEADERS)
|
15
|
+
additional_jwt_headers = key_id ? {kid: key_id} : {}
|
16
|
+
JWT.encode(
|
17
|
+
Claims.generate(
|
18
|
+
method: method,
|
19
|
+
path: path,
|
20
|
+
headers: headers,
|
21
|
+
body: body,
|
22
|
+
additional_headers_to_sign: additional_headers_to_sign,
|
23
|
+
issuer: issuer
|
24
|
+
),
|
25
|
+
secret_key,
|
26
|
+
algorithm,
|
27
|
+
additional_jwt_headers
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.verify(request:, secret_key:, algorithm: nil)
|
32
|
+
jwt_token = Headers.fetch('Authorization', request)
|
33
|
+
algorithm ||= DEFAULT_ALGORITHM
|
34
|
+
|
35
|
+
if jwt_token.nil?
|
36
|
+
raise MissingAuthorizationHeaderError, "Missing Authorization header in the request"
|
37
|
+
end
|
38
|
+
|
39
|
+
begin
|
40
|
+
claims = JWT.decode(jwt_token, secret_key, algorithm)[0]
|
41
|
+
unless verified_request?(request: request, claims: claims)
|
42
|
+
raise RequestVerificationFailedError, "Request failed verification"
|
43
|
+
end
|
44
|
+
|
45
|
+
rescue ::JWT::DecodeError => e
|
46
|
+
raise JWTDecodeError, e.message
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.verified_request?(request:, claims:)
|
51
|
+
claims['method'].downcase == request.request_method.downcase &&
|
52
|
+
claims['path'] == request.fullpath &&
|
53
|
+
claims['body_sha'] == Digest::SHA256.hexdigest(request.body.read || "") &&
|
54
|
+
verified_headers?(request: request, claims: claims)
|
55
|
+
end
|
56
|
+
|
57
|
+
private_class_method :verified_request?
|
58
|
+
|
59
|
+
def self.verified_headers?(request:, claims:)
|
60
|
+
parsed_headers = JSON.parse(claims['headers'])
|
61
|
+
|
62
|
+
parsed_headers.all? do |header_key, header_value|
|
63
|
+
Headers.fetch(header_key, request) == header_value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private_class_method :verified_headers?
|
68
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jwt_signed_request
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Toan Nguyen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-09-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: jwt
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rack
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
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: bundler
|
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: rake
|
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
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rack-test
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: timecop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: ''
|
112
|
+
email:
|
113
|
+
- toan.nguyen@envato.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- README.md
|
119
|
+
- lib/jwt_signed_request.rb
|
120
|
+
- lib/jwt_signed_request/claims.rb
|
121
|
+
- lib/jwt_signed_request/headers.rb
|
122
|
+
- lib/jwt_signed_request/middlewares/faraday.rb
|
123
|
+
- lib/jwt_signed_request/middlewares/rack.rb
|
124
|
+
- lib/jwt_signed_request/version.rb
|
125
|
+
homepage: https://github.com/envato/jwt_signed_request
|
126
|
+
licenses: []
|
127
|
+
metadata: {}
|
128
|
+
post_install_message:
|
129
|
+
rdoc_options: []
|
130
|
+
require_paths:
|
131
|
+
- lib
|
132
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
requirements: []
|
143
|
+
rubyforge_project:
|
144
|
+
rubygems_version: 2.4.5.1
|
145
|
+
signing_key:
|
146
|
+
specification_version: 4
|
147
|
+
summary: JWT request signing and verification for Internal APIs
|
148
|
+
test_files: []
|
149
|
+
has_rdoc:
|