jwt_signed_request 1.2.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +50 -44
- data/lib/jwt_signed_request/claims.rb +12 -7
- data/lib/jwt_signed_request/key_store.rb +32 -0
- data/lib/jwt_signed_request/middlewares/faraday.rb +1 -1
- data/lib/jwt_signed_request/middlewares/rack.rb +5 -3
- data/lib/jwt_signed_request/sign.rb +68 -0
- data/lib/jwt_signed_request/verify.rb +96 -0
- data/lib/jwt_signed_request/version.rb +1 -1
- data/lib/jwt_signed_request.rb +16 -73
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6fb3525b6977202315ff48f351757ed3e9c6c010
|
4
|
+
data.tar.gz: 3e7f295a2d568f8350d67354f03433f4869b72e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 537683653302ef5695e2ec9cf78ad2e368aff14db1bdf854ed16aa0ed82a5902457e980d0594b297f591fe1837da9aee7871ed4c8c10bb7099a52e1f0eadcab5
|
7
|
+
data.tar.gz: e29633992973869ceb8f8857f8b7bef580f5bf632c8b80e26a141c9501eb8e51625916d250481580f26ce22d4a5c429f9693e924825d46b2687852bf148ee99b
|
data/README.md
CHANGED
@@ -28,9 +28,46 @@ $ openssl ec -in myprivatekey.pem -pubout -out mypubkey.pem
|
|
28
28
|
|
29
29
|
Store and encrypt these in your application secrets.
|
30
30
|
|
31
|
+
## Configuration
|
32
|
+
|
33
|
+
You can add signing and verification keys to the key store as your application needs them.
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
private_key = <<-pem.gsub(/^\s+/, "")
|
37
|
+
-----BEGIN EC PRIVATE KEY-----
|
38
|
+
MHcCAQEEIBOQ3YIILYMV1glTKbF9oeZWzHe3SNQjAx4IbPIxNygQoAoGCCqGSM49
|
39
|
+
AwEHoUQDQgAEuOC3ufTTnW0hVmCPNERb4LxaDE/OexDdlmXEjHYaixzYIduluGXd
|
40
|
+
3cjg4H2gjqsY/NCpJ9nM8/AAINSrq+qPuA==
|
41
|
+
-----END EC PRIVATE KEY-----
|
42
|
+
pem
|
43
|
+
|
44
|
+
public_key = <<-pem.gsub(/^\s+/, "")
|
45
|
+
-----BEGIN PUBLIC KEY-----
|
46
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuOC3ufTTnW0hVmCPNERb4LxaDE/O
|
47
|
+
exDdlmXEjHYaixzYIduluGXd3cjg4H2gjqsY/NCpJ9nM8/AAINSrq+qPuA==
|
48
|
+
-----END PUBLIC KEY-----
|
49
|
+
pem
|
50
|
+
|
51
|
+
require 'openssl'
|
52
|
+
|
53
|
+
JWTSignedRequest.configure_keys do |config|
|
54
|
+
config.add_signing_key(
|
55
|
+
key_id: 'client_a',
|
56
|
+
key: OpenSSL::PKey::EC.new(private_key),
|
57
|
+
algorithm: 'ES256',
|
58
|
+
)
|
59
|
+
|
60
|
+
config.add_verification_key(
|
61
|
+
key_id: 'client_a',
|
62
|
+
key: OpenSSL::PKey::EC.new(public_key),
|
63
|
+
algorithm: 'ES256',
|
64
|
+
)
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
31
68
|
## Signing Requests
|
32
69
|
|
33
|
-
If you
|
70
|
+
If you have added your signing keys to the key store, you will only need to specify the `key_id` you are signing the requests with.
|
34
71
|
|
35
72
|
### Using net/http
|
36
73
|
|
@@ -40,14 +77,6 @@ require 'uri'
|
|
40
77
|
require 'openssl'
|
41
78
|
require 'jwt_signed_request'
|
42
79
|
|
43
|
-
private_key = """
|
44
|
-
-----BEGIN EC PRIVATE KEY-----
|
45
|
-
MHcCAQEEIBOQ3YIILYMV1glTKbF9oeZWzHe3SNQjAx4IbPIxNygQoAoGCCqGSM49
|
46
|
-
AwEHoUQDQgAEuOC3ufTTnW0hVmCPNERb4LxaDE/OexDdlmXEjHYaixzYIduluGXd
|
47
|
-
3cjg4H2gjqsY/NCpJ9nM8/AAINSrq+qPuA==
|
48
|
-
-----END EC PRIVATE KEY-----
|
49
|
-
"""
|
50
|
-
|
51
80
|
uri = URI('http://example.com')
|
52
81
|
req = Net::HTTP::Get.new(uri)
|
53
82
|
|
@@ -56,9 +85,7 @@ req['Authorization'] = JWTSignedRequest.sign(
|
|
56
85
|
path: req.path,
|
57
86
|
headers: {"Content-Type" => "application/json"},
|
58
87
|
body: "",
|
59
|
-
|
60
|
-
algorithm: 'ES256', # optional (default: ES256)
|
61
|
-
key_id: 'my-key-id', # optional
|
88
|
+
key_id: 'my-key-id',
|
62
89
|
issuer: 'my-issuer' # optional
|
63
90
|
additional_headers_to_sign: ['X-AUTH'] # optional
|
64
91
|
)
|
@@ -75,19 +102,9 @@ require 'faraday'
|
|
75
102
|
require 'openssl'
|
76
103
|
require 'jwt_signed_request/middlewares/faraday'
|
77
104
|
|
78
|
-
private_key = """
|
79
|
-
-----BEGIN EC PRIVATE KEY-----
|
80
|
-
MHcCAQEEIBOQ3YIILYMV1glTKbF9oeZWzHe3SNQjAx4IbPIxNygQoAoGCCqGSM49
|
81
|
-
AwEHoUQDQgAEuOC3ufTTnW0hVmCPNERb4LxaDE/OexDdlmXEjHYaixzYIduluGXd
|
82
|
-
3cjg4H2gjqsY/NCpJ9nM8/AAINSrq+qPuA==
|
83
|
-
-----END EC PRIVATE KEY-----
|
84
|
-
"""
|
85
|
-
|
86
105
|
conn = Faraday.new(url: URI.parse('http://example.com')) do |faraday|
|
87
106
|
faraday.use JWTSignedRequest::Middlewares::Faraday,
|
88
|
-
|
89
|
-
algorithm: 'ES256', # optional (default: ES256)
|
90
|
-
key_id: 'my-key-id', # optional
|
107
|
+
key_id: 'my-key-id',
|
91
108
|
issuer: 'my-issuer', # optional
|
92
109
|
additional_headers_to_sign: ['X-AUTH'] # optional
|
93
110
|
|
@@ -102,19 +119,13 @@ end
|
|
102
119
|
|
103
120
|
## Verifying Requests
|
104
121
|
|
105
|
-
|
122
|
+
Please make sure you have added your verification keys to the key store. Doing so will allow the server to verify requests signed by different signing keys.
|
123
|
+
|
106
124
|
|
107
125
|
## Using Rails
|
108
126
|
|
109
127
|
```ruby
|
110
128
|
class APIController < ApplicationController
|
111
|
-
PUBLIC_KEY = """
|
112
|
-
-----BEGIN PUBLIC KEY-----
|
113
|
-
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuOC3ufTTnW0hVmCPNERb4LxaDE/O
|
114
|
-
exDdlmXEjHYaixzYIduluGXd3cjg4H2gjqsY/NCpJ9nM8/AAINSrq+qPuA==
|
115
|
-
-----END PUBLIC KEY-----
|
116
|
-
"""
|
117
|
-
|
118
129
|
before_action :verify_request
|
119
130
|
|
120
131
|
...
|
@@ -123,10 +134,7 @@ exDdlmXEjHYaixzYIduluGXd3cjg4H2gjqsY/NCpJ9nM8/AAINSrq+qPuA==
|
|
123
134
|
|
124
135
|
def verify_request
|
125
136
|
begin
|
126
|
-
JWTSignedRequest.verify(
|
127
|
-
request: request,
|
128
|
-
secret_key: OpenSSL::PKey::EC.new(PUBLIC_KEY)
|
129
|
-
)
|
137
|
+
JWTSignedRequest.verify(request: request)
|
130
138
|
|
131
139
|
rescue JWTSignedRequest::UnauthorizedRequestError => e
|
132
140
|
render :json => {}, :status => :unauthorized
|
@@ -141,26 +149,24 @@ end
|
|
141
149
|
JWT tokens contain an expiry timestamp. If communication delays are large (or system clocks are sufficiently out of synch), you may need to increase the 'leeway' when verifying. For example:
|
142
150
|
|
143
151
|
```ruby
|
144
|
-
JWTSignedRequest.verify(request: request,
|
152
|
+
JWTSignedRequest.verify(request: request, leeway: 55)
|
145
153
|
```
|
146
154
|
|
147
155
|
## Using Rack Middleware
|
148
156
|
|
149
157
|
```ruby
|
150
|
-
PUBLIC_KEY = """
|
151
|
-
-----BEGIN PUBLIC KEY-----
|
152
|
-
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuOC3ufTTnW0hVmCPNERb4LxaDE/O
|
153
|
-
exDdlmXEjHYaixzYIduluGXd3cjg4H2gjqsY/NCpJ9nM8/AAINSrq+qPuA==
|
154
|
-
-----END PUBLIC KEY-----
|
155
|
-
"""
|
156
|
-
|
157
158
|
class Server < Sinatra::Base
|
158
159
|
use JWTSignedRequest::Middlewares::Rack,
|
159
|
-
secret_key: OpenSSL::PKey::EC.new(PUBLIC_KEY),
|
160
160
|
exclude_paths: /public|health/ # optional regex
|
161
161
|
end
|
162
162
|
```
|
163
163
|
|
164
|
+
## Backwards Compability
|
165
|
+
|
166
|
+
Please note that the way we sign and verify requests has changed in version 2.x.x. For documentation on how to use older versions please look [here](https://github.com/envato/jwt_signed_request/blob/master/VERSION_1.md).
|
167
|
+
|
168
|
+
We are only supporting the old API for the next couple of releases of version 2.x.x so please upgrade ASAP.
|
169
|
+
|
164
170
|
## Maintainers
|
165
171
|
- [Toan Nguyen](https://github.com/yoshdog)
|
166
172
|
|
@@ -4,19 +4,16 @@ require 'rack/utils'
|
|
4
4
|
|
5
5
|
module JWTSignedRequest
|
6
6
|
class Claims
|
7
|
-
EMPTY_HEADERS = [].freeze
|
8
|
-
|
9
7
|
def self.generate(args)
|
10
8
|
new(**args).generate
|
11
9
|
end
|
12
10
|
|
13
|
-
def initialize(method:, path:, headers:, body:, additional_headers_to_sign
|
11
|
+
def initialize(method:, path:, headers:, body:, additional_headers_to_sign:, issuer:)
|
14
12
|
@method = method
|
15
13
|
@path = path
|
16
|
-
@headers = headers
|
14
|
+
@headers = headers || EMPTY_HEADERS
|
17
15
|
@body = body
|
18
|
-
@additional_headers_to_sign = additional_headers_to_sign
|
19
|
-
@timeout = timeout
|
16
|
+
@additional_headers_to_sign = additional_headers_to_sign || EMPTY_HEADERS
|
20
17
|
@issuer = issuer
|
21
18
|
end
|
22
19
|
|
@@ -36,7 +33,7 @@ module JWTSignedRequest
|
|
36
33
|
|
37
34
|
private
|
38
35
|
|
39
|
-
attr_reader :method, :path, :headers, :body, :additional_headers_to_sign, :
|
36
|
+
attr_reader :method, :path, :headers, :body, :additional_headers_to_sign, :issuer
|
40
37
|
|
41
38
|
HEADERS_TO_SIGN = %w(
|
42
39
|
Content-Type
|
@@ -51,6 +48,14 @@ module JWTSignedRequest
|
|
51
48
|
|
52
49
|
private_constant :DEFAULT_TIMEOUT
|
53
50
|
|
51
|
+
EMPTY_HEADERS = [].freeze
|
52
|
+
|
53
|
+
private_constant :EMPTY_HEADERS
|
54
|
+
|
55
|
+
def timeout
|
56
|
+
DEFAULT_TIMEOUT
|
57
|
+
end
|
58
|
+
|
54
59
|
def formatted_body
|
55
60
|
case body
|
56
61
|
when String
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module JWTSignedRequest
|
2
|
+
class KeyStore
|
3
|
+
def initialize
|
4
|
+
@signing_keys = {}
|
5
|
+
@verification_keys = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_signing_key(key_id:, key:, algorithm:)
|
9
|
+
@signing_keys.store(key_id,
|
10
|
+
{
|
11
|
+
key: key,
|
12
|
+
algorithm: algorithm
|
13
|
+
})
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_verification_key(key_id:, key:, algorithm:)
|
17
|
+
@verification_keys.store(key_id,
|
18
|
+
{
|
19
|
+
key: key,
|
20
|
+
algorithm: algorithm
|
21
|
+
})
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_signing_key(key_id:)
|
25
|
+
@signing_keys.fetch(key_id, {})
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_verification_key(key_id:)
|
29
|
+
@verification_keys.fetch(key_id, {})
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -15,7 +15,6 @@ module JWTSignedRequest
|
|
15
15
|
path: env[:url].request_uri,
|
16
16
|
headers: env[:request_headers],
|
17
17
|
body: env.fetch(:body, ::JWTSignedRequest::EMPTY_BODY),
|
18
|
-
secret_key: options[:secret_key],
|
19
18
|
**optional_settings
|
20
19
|
)
|
21
20
|
|
@@ -29,6 +28,7 @@ module JWTSignedRequest
|
|
29
28
|
|
30
29
|
def optional_settings
|
31
30
|
{
|
31
|
+
secret_key: options[:secret_key],
|
32
32
|
algorithm: options[:algorithm],
|
33
33
|
additional_headers_to_sign: options[:additional_headers_to_sign],
|
34
34
|
key_id: options[:key_id],
|
@@ -8,7 +8,7 @@ module JWTSignedRequest
|
|
8
8
|
|
9
9
|
def initialize(app, options = {})
|
10
10
|
@app = app
|
11
|
-
@secret_key = options
|
11
|
+
@secret_key = options[:secret_key]
|
12
12
|
@algorithm = options[:algorithm]
|
13
13
|
@exclude_paths = options[:exclude_paths]
|
14
14
|
end
|
@@ -16,11 +16,13 @@ module JWTSignedRequest
|
|
16
16
|
def call(env)
|
17
17
|
begin
|
18
18
|
unless excluded_path?(env)
|
19
|
-
|
19
|
+
args = {
|
20
20
|
request: ::Rack::Request.new(env),
|
21
21
|
secret_key: secret_key,
|
22
22
|
algorithm: algorithm
|
23
|
-
|
23
|
+
}.reject { |_, value| value.nil? }
|
24
|
+
|
25
|
+
::JWTSignedRequest.verify(**args)
|
24
26
|
end
|
25
27
|
|
26
28
|
app.call(env)
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'jwt_signed_request/claims'
|
2
|
+
|
3
|
+
module JWTSignedRequest
|
4
|
+
class Sign
|
5
|
+
def self.call(*args)
|
6
|
+
new(*args).call
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(
|
10
|
+
method:,
|
11
|
+
path:,
|
12
|
+
body: EMPTY_BODY,
|
13
|
+
headers:,
|
14
|
+
secret_key: nil,
|
15
|
+
algorithm: nil,
|
16
|
+
key_id: nil,
|
17
|
+
issuer: nil,
|
18
|
+
additional_headers_to_sign: nil
|
19
|
+
)
|
20
|
+
@method = method
|
21
|
+
@path = path
|
22
|
+
@body = body
|
23
|
+
@headers = headers
|
24
|
+
@secret_key = secret_key
|
25
|
+
@algorithm = algorithm
|
26
|
+
@key_id = key_id
|
27
|
+
@issuer = issuer
|
28
|
+
@additional_headers_to_sign = additional_headers_to_sign
|
29
|
+
end
|
30
|
+
|
31
|
+
def call
|
32
|
+
JWT.encode(claims, secret_key, algorithm, additional_jwt_headers)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_reader \
|
38
|
+
:method, :path, :body, :headers,
|
39
|
+
:key_id, :issuer, :additional_headers_to_sign
|
40
|
+
|
41
|
+
def stored_key
|
42
|
+
@stored_key ||= JWTSignedRequest.key_store.get_signing_key(key_id: key_id)
|
43
|
+
end
|
44
|
+
|
45
|
+
def secret_key
|
46
|
+
@secret_key ||= stored_key.fetch(:key) { raise MissingKeyIdError }
|
47
|
+
end
|
48
|
+
|
49
|
+
def algorithm
|
50
|
+
@algorithm ||= stored_key.fetch(:algorithm, DEFAULT_ALGORITHM)
|
51
|
+
end
|
52
|
+
|
53
|
+
def additional_jwt_headers
|
54
|
+
key_id ? {kid: key_id} : {}
|
55
|
+
end
|
56
|
+
|
57
|
+
def claims
|
58
|
+
Claims.generate(
|
59
|
+
method: method,
|
60
|
+
path: path,
|
61
|
+
headers: headers,
|
62
|
+
body: body,
|
63
|
+
additional_headers_to_sign: additional_headers_to_sign,
|
64
|
+
issuer: issuer
|
65
|
+
)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'jwt_signed_request/headers'
|
2
|
+
|
3
|
+
module JWTSignedRequest
|
4
|
+
class Verify
|
5
|
+
def self.call(*args)
|
6
|
+
new(*args).call
|
7
|
+
end
|
8
|
+
|
9
|
+
# TODO: secret_key & algorithm is deprecated and will be removed in future.
|
10
|
+
# For now we will support its functionaility
|
11
|
+
def initialize(request:, secret_key: nil, algorithm: nil, leeway: nil)
|
12
|
+
@request = request
|
13
|
+
@secret_key = secret_key
|
14
|
+
@algorithm = algorithm
|
15
|
+
@leeway = leeway
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
if jwt_token.nil?
|
20
|
+
raise MissingAuthorizationHeaderError, "Missing Authorization header in the request"
|
21
|
+
end
|
22
|
+
|
23
|
+
unless verified_request?
|
24
|
+
raise RequestVerificationFailedError, "Request failed verification"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :request, :leeway
|
31
|
+
|
32
|
+
def stored_key
|
33
|
+
jwt_header, _, _, _ = ::JWT.decoded_segments(jwt_token, false)
|
34
|
+
key_id = jwt_header.fetch('kid') { raise MissingKeyIdError }
|
35
|
+
signed_algorithm = jwt_header.fetch('alg')
|
36
|
+
JWTSignedRequest.key_store.get_verification_key(key_id: key_id).tap do |key|
|
37
|
+
if signed_algorithm != key[:algorithm]
|
38
|
+
raise AlgorithmMismatchError
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def secret_key
|
44
|
+
@secret_key ||= stored_key.fetch(:key) { raise MissingKeyIdError }
|
45
|
+
end
|
46
|
+
|
47
|
+
def jwt_token
|
48
|
+
@jwt_token ||= Headers.fetch('Authorization', request)
|
49
|
+
end
|
50
|
+
|
51
|
+
def claims
|
52
|
+
@claims ||= begin
|
53
|
+
verify = true
|
54
|
+
options = {}
|
55
|
+
|
56
|
+
if leeway
|
57
|
+
# TODO: Once JWT v2.0.0 has been released, we should upgrade to it
|
58
|
+
# and start using `exp_leeway` instead 'leeway' will still work, but
|
59
|
+
# 'exp_leeway' is more explicit and is the documented way to do it.
|
60
|
+
#
|
61
|
+
# See https://github.com/jwt/ruby-jwt/pull/187.
|
62
|
+
options[:leeway] = leeway.to_i
|
63
|
+
end
|
64
|
+
|
65
|
+
JWT.decode(jwt_token, secret_key, verify, options)[0]
|
66
|
+
rescue ::JWT::DecodeError => e
|
67
|
+
raise JWTDecodeError, e.message
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def verified_request?
|
72
|
+
claims['method'].to_s.downcase == request.request_method.downcase &&
|
73
|
+
claims['path'] == request.fullpath &&
|
74
|
+
claims['body_sha'] == Digest::SHA256.hexdigest(request_body) &&
|
75
|
+
verified_headers?
|
76
|
+
end
|
77
|
+
|
78
|
+
def request_body
|
79
|
+
string = request.body.read
|
80
|
+
request.body.rewind
|
81
|
+
string
|
82
|
+
end
|
83
|
+
|
84
|
+
def verified_headers?
|
85
|
+
parsed_headers = begin
|
86
|
+
JSON.parse(claims['headers'].to_s)
|
87
|
+
rescue JSON::ParserError
|
88
|
+
{}
|
89
|
+
end
|
90
|
+
|
91
|
+
parsed_headers.all? do |header_key, header_value|
|
92
|
+
Headers.fetch(header_key, request) == header_value
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/jwt_signed_request.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
require 'jwt'
|
2
|
-
require 'jwt_signed_request/
|
3
|
-
require 'jwt_signed_request/
|
2
|
+
require 'jwt_signed_request/key_store'
|
3
|
+
require 'jwt_signed_request/sign'
|
4
|
+
require 'jwt_signed_request/verify'
|
4
5
|
|
5
6
|
module JWTSignedRequest
|
7
|
+
extend self
|
8
|
+
|
6
9
|
DEFAULT_ALGORITHM = 'ES256'.freeze
|
7
10
|
EMPTY_BODY = "".freeze
|
8
11
|
|
@@ -10,83 +13,23 @@ module JWTSignedRequest
|
|
10
13
|
MissingAuthorizationHeaderError = Class.new(UnauthorizedRequestError)
|
11
14
|
JWTDecodeError = Class.new(UnauthorizedRequestError)
|
12
15
|
RequestVerificationFailedError = Class.new(UnauthorizedRequestError)
|
16
|
+
MissingKeyIdError = Class.new(UnauthorizedRequestError)
|
17
|
+
UnknownKeyIdError = Class.new(UnauthorizedRequestError)
|
18
|
+
AlgorithmMismatchError = Class.new(UnauthorizedRequestError)
|
13
19
|
|
14
|
-
def
|
15
|
-
|
16
|
-
secret_key:, algorithm: DEFAULT_ALGORITHM,
|
17
|
-
key_id: nil, issuer: nil,
|
18
|
-
additional_headers_to_sign: Claims::EMPTY_HEADERS)
|
19
|
-
additional_jwt_headers = key_id ? {kid: key_id} : {}
|
20
|
-
JWT.encode(
|
21
|
-
Claims.generate(
|
22
|
-
method: method,
|
23
|
-
path: path,
|
24
|
-
headers: headers,
|
25
|
-
body: body,
|
26
|
-
additional_headers_to_sign: additional_headers_to_sign,
|
27
|
-
issuer: issuer
|
28
|
-
),
|
29
|
-
secret_key,
|
30
|
-
algorithm,
|
31
|
-
additional_jwt_headers
|
32
|
-
)
|
33
|
-
end
|
34
|
-
|
35
|
-
def self.verify(request:, secret_key:, algorithm: nil, leeway: nil)
|
36
|
-
# TODO: algorithm is deprecated and will be removed in future
|
37
|
-
verify = true
|
38
|
-
options = {}
|
39
|
-
if leeway
|
40
|
-
# TODO: Once JWT v2.0.0 has been released, we should upgrade to it and start using `exp_leeway` instead
|
41
|
-
# 'leeway' will still work, but 'exp_leeway' is more explicit and is the documented way to do it.
|
42
|
-
# see https://github.com/jwt/ruby-jwt/pull/187
|
43
|
-
options[:leeway] = leeway.to_i
|
44
|
-
end
|
45
|
-
jwt_token = Headers.fetch('Authorization', request)
|
46
|
-
|
47
|
-
if jwt_token.nil?
|
48
|
-
raise MissingAuthorizationHeaderError, "Missing Authorization header in the request"
|
49
|
-
end
|
50
|
-
|
51
|
-
begin
|
52
|
-
claims = JWT.decode(jwt_token, secret_key, verify, options)[0]
|
53
|
-
unless verified_request?(request: request, claims: claims)
|
54
|
-
raise RequestVerificationFailedError, "Request failed verification"
|
55
|
-
end
|
56
|
-
|
57
|
-
rescue ::JWT::DecodeError => e
|
58
|
-
raise JWTDecodeError, e.message
|
59
|
-
end
|
20
|
+
def configure_keys
|
21
|
+
yield(key_store)
|
60
22
|
end
|
61
23
|
|
62
|
-
def
|
63
|
-
|
64
|
-
claims['path'] == request.fullpath &&
|
65
|
-
claims['body_sha'] == Digest::SHA256.hexdigest(request_body(request: request)) &&
|
66
|
-
verified_headers?(request: request, claims: claims)
|
24
|
+
def key_store
|
25
|
+
@key_store ||= KeyStore.new
|
67
26
|
end
|
68
27
|
|
69
|
-
|
70
|
-
|
71
|
-
def self.request_body(request:)
|
72
|
-
string = request.body.read
|
73
|
-
request.body.rewind
|
74
|
-
string
|
28
|
+
def sign(*args)
|
29
|
+
Sign.call(*args)
|
75
30
|
end
|
76
31
|
|
77
|
-
|
78
|
-
|
79
|
-
def self.verified_headers?(request:, claims:)
|
80
|
-
parsed_headers = begin
|
81
|
-
JSON.parse(claims['headers'].to_s)
|
82
|
-
rescue JSON::ParserError
|
83
|
-
{}
|
84
|
-
end
|
85
|
-
|
86
|
-
parsed_headers.all? do |header_key, header_value|
|
87
|
-
Headers.fetch(header_key, request) == header_value
|
88
|
-
end
|
32
|
+
def verify(*args)
|
33
|
+
Verify.call(*args)
|
89
34
|
end
|
90
|
-
|
91
|
-
private_class_method :verified_headers?
|
92
35
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jwt_signed_request
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Toan Nguyen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-06-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jwt
|
@@ -119,8 +119,11 @@ files:
|
|
119
119
|
- lib/jwt_signed_request.rb
|
120
120
|
- lib/jwt_signed_request/claims.rb
|
121
121
|
- lib/jwt_signed_request/headers.rb
|
122
|
+
- lib/jwt_signed_request/key_store.rb
|
122
123
|
- lib/jwt_signed_request/middlewares/faraday.rb
|
123
124
|
- lib/jwt_signed_request/middlewares/rack.rb
|
125
|
+
- lib/jwt_signed_request/sign.rb
|
126
|
+
- lib/jwt_signed_request/verify.rb
|
124
127
|
- lib/jwt_signed_request/version.rb
|
125
128
|
homepage: https://github.com/envato/jwt_signed_request
|
126
129
|
licenses: []
|
@@ -141,7 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
144
|
version: '0'
|
142
145
|
requirements: []
|
143
146
|
rubyforge_project:
|
144
|
-
rubygems_version: 2.
|
147
|
+
rubygems_version: 2.6.11
|
145
148
|
signing_key:
|
146
149
|
specification_version: 4
|
147
150
|
summary: JWT request signing and verification for Internal APIs
|