jwt_signed_request 1.2.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|