jwt_signed_request 2.5.4 → 2.6.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 +64 -18
- data/lib/jwt_signed_request.rb +6 -6
- data/lib/jwt_signed_request/key_store.rb +18 -10
- data/lib/jwt_signed_request/middlewares/faraday.rb +5 -14
- data/lib/jwt_signed_request/middlewares/rack.rb +21 -22
- data/lib/jwt_signed_request/sign.rb +19 -5
- data/lib/jwt_signed_request/verify.rb +9 -4
- data/lib/jwt_signed_request/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb9e7a555755580bb4b37387799d44d3bc4d67d7a157060fce83183827250d15
|
4
|
+
data.tar.gz: b4ed152d2dfa73fc961280a6d7c09fd480565195c280f76b71e6326268ba894d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d7f1c2fe8ffac7c069d11231ae797fb92967cece0ed2c441167d02ebec178edb6d9474d02a0e4ec3d9a4840d8df0888cb6b00029f99190e0b24f1fceaf2f0f2
|
7
|
+
data.tar.gz: 0b38896799b5021d54266010534f5360819d95363b7f09a276e7b729df8d708bacd9c861e1ae194e3b6f2bd7894e7f32f2e1e60d486d00b07b84705d2cbc4713
|
data/README.md
CHANGED
@@ -30,24 +30,32 @@ Store and encrypt these in your application secrets.
|
|
30
30
|
|
31
31
|
## Configuration
|
32
32
|
|
33
|
-
You can add signing and verification keys to
|
33
|
+
You can add signing and verification keys to one or more key stores as your application needs them.
|
34
|
+
|
35
|
+
For example, given the following keys:
|
34
36
|
|
35
37
|
```ruby
|
36
|
-
private_key = <<-
|
38
|
+
private_key = <<-PEM.gsub(/^\s+/, "")
|
37
39
|
-----BEGIN EC PRIVATE KEY-----
|
38
40
|
MHcCAQEEIBOQ3YIILYMV1glTKbF9oeZWzHe3SNQjAx4IbPIxNygQoAoGCCqGSM49
|
39
41
|
AwEHoUQDQgAEuOC3ufTTnW0hVmCPNERb4LxaDE/OexDdlmXEjHYaixzYIduluGXd
|
40
42
|
3cjg4H2gjqsY/NCpJ9nM8/AAINSrq+qPuA==
|
41
43
|
-----END EC PRIVATE KEY-----
|
42
|
-
|
44
|
+
PEM
|
43
45
|
|
44
|
-
public_key = <<-
|
46
|
+
public_key = <<-PEM.gsub(/^\s+/, "")
|
45
47
|
-----BEGIN PUBLIC KEY-----
|
46
48
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuOC3ufTTnW0hVmCPNERb4LxaDE/O
|
47
49
|
exDdlmXEjHYaixzYIduluGXd3cjg4H2gjqsY/NCpJ9nM8/AAINSrq+qPuA==
|
48
50
|
-----END PUBLIC KEY-----
|
49
|
-
|
51
|
+
PEM
|
52
|
+
```
|
53
|
+
|
54
|
+
### Single key store
|
55
|
+
|
56
|
+
If your application only needs a single key store, configure it like so:
|
50
57
|
|
58
|
+
```ruby
|
51
59
|
require 'openssl'
|
52
60
|
|
53
61
|
JWTSignedRequest.configure_keys do |config|
|
@@ -65,9 +73,35 @@ JWTSignedRequest.configure_keys do |config|
|
|
65
73
|
end
|
66
74
|
```
|
67
75
|
|
76
|
+
### Multiple key stores
|
77
|
+
|
78
|
+
If your application requires multiple key stores, configure them like so:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
key_store_id = 'widget_admin'
|
82
|
+
|
83
|
+
JWTSignedRequest.configure_keys(key_store_id) do |config|
|
84
|
+
config.add_signing_key(
|
85
|
+
key_id: 'client_a',
|
86
|
+
key: OpenSSL::PKey::EC.new(private_key),
|
87
|
+
algorithm: 'ES256',
|
88
|
+
)
|
89
|
+
|
90
|
+
config.add_verification_key(
|
91
|
+
key_id: 'client_a',
|
92
|
+
key: OpenSSL::PKey::EC.new(public_key),
|
93
|
+
algorithm: 'ES256',
|
94
|
+
)
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
68
98
|
## Signing Requests
|
69
99
|
|
70
|
-
If you have added your signing keys to
|
100
|
+
If you have added your signing keys to a key store, you will only need to
|
101
|
+
specify the `key_id` you are signing the requests with.
|
102
|
+
|
103
|
+
If you are using multiple key stores, you will also need to pass the
|
104
|
+
appropriate `key_store_id`.
|
71
105
|
|
72
106
|
### Using net/http
|
73
107
|
|
@@ -86,6 +120,7 @@ jwt_token = JWTSignedRequest.sign(
|
|
86
120
|
body: "",
|
87
121
|
key_id: 'my-key-id', # used for looking up key and kid header
|
88
122
|
lookup_key_id: 'my-alt-key-id', # optionally override lookup key
|
123
|
+
key_store_id: 'widget_admin', # optionally specify named key store ID
|
89
124
|
issuer: 'my-issuer' # optional
|
90
125
|
additional_headers_to_sign: ['X-AUTH'] # optional
|
91
126
|
)
|
@@ -97,7 +132,7 @@ res = Net::HTTP.start(uri.hostname, uri.port) {|http|
|
|
97
132
|
}
|
98
133
|
```
|
99
134
|
|
100
|
-
### Using
|
135
|
+
### Using Faraday
|
101
136
|
|
102
137
|
```ruby
|
103
138
|
require 'faraday'
|
@@ -105,11 +140,14 @@ require 'openssl'
|
|
105
140
|
require 'jwt_signed_request/middlewares/faraday'
|
106
141
|
|
107
142
|
conn = Faraday.new(url: URI.parse('http://example.com')) do |faraday|
|
108
|
-
faraday.use
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
143
|
+
faraday.use(
|
144
|
+
JWTSignedRequest::Middlewares::Faraday,
|
145
|
+
key_id: 'my-key-id',
|
146
|
+
key_store_id: 'my-key-store-id', # optional
|
147
|
+
issuer: 'my-issuer', # optional
|
148
|
+
additional_headers_to_sign: ['X-AUTH'], # optional
|
149
|
+
bearer_schema: true, # optional
|
150
|
+
)
|
113
151
|
|
114
152
|
faraday.adapter Faraday.default_adapter
|
115
153
|
end
|
@@ -134,8 +172,9 @@ Determines whether to use the [Bearer schema](https://auth0.com/docs/jwt#how-do-
|
|
134
172
|
|
135
173
|
## Verifying Requests
|
136
174
|
|
137
|
-
Please make sure you have added your verification keys to the key
|
138
|
-
|
175
|
+
Please make sure you have added your verification keys to the appropriate key
|
176
|
+
store. Doing so will allow the server to verify requests signed by different
|
177
|
+
signing keys.
|
139
178
|
|
140
179
|
## Using Rails
|
141
180
|
|
@@ -149,7 +188,11 @@ class APIController < ApplicationController
|
|
149
188
|
|
150
189
|
def verify_request
|
151
190
|
begin
|
152
|
-
JWTSignedRequest.verify(
|
191
|
+
JWTSignedRequest.verify(
|
192
|
+
request: request,
|
193
|
+
# Use optional `key_store_id` kwarg when working with multiple key stores, eg:
|
194
|
+
key_store_id: 'widget_admin',
|
195
|
+
)
|
153
196
|
|
154
197
|
rescue JWTSignedRequest::UnauthorizedRequestError => e
|
155
198
|
render :json => {}, :status => :unauthorized
|
@@ -171,9 +214,12 @@ JWT tokens contain an expiry timestamp. If communication delays are large (or sy
|
|
171
214
|
|
172
215
|
```ruby
|
173
216
|
class Server < Sinatra::Base
|
174
|
-
use
|
175
|
-
|
176
|
-
|
217
|
+
use(
|
218
|
+
JWTSignedRequest::Middlewares::Rack,
|
219
|
+
exclude_paths: /public|health/, # optional regex
|
220
|
+
leeway: 100, # optional
|
221
|
+
key_store_id: 'my-key-store-id', # optional
|
222
|
+
)
|
177
223
|
end
|
178
224
|
```
|
179
225
|
|
data/lib/jwt_signed_request.rb
CHANGED
@@ -9,15 +9,15 @@ require 'jwt_signed_request/errors'
|
|
9
9
|
module JWTSignedRequest
|
10
10
|
extend self
|
11
11
|
|
12
|
-
DEFAULT_ALGORITHM = 'ES256'
|
13
|
-
EMPTY_BODY =
|
12
|
+
DEFAULT_ALGORITHM = 'ES256'
|
13
|
+
EMPTY_BODY = ''
|
14
14
|
|
15
|
-
def configure_keys
|
16
|
-
yield(
|
15
|
+
def configure_keys(key_store_id = nil)
|
16
|
+
yield KeyStore.find(key_store_id)
|
17
17
|
end
|
18
18
|
|
19
|
-
def key_store
|
20
|
-
|
19
|
+
def key_store(id = nil)
|
20
|
+
KeyStore.find(id)
|
21
21
|
end
|
22
22
|
|
23
23
|
def sign(**args)
|
@@ -2,25 +2,33 @@
|
|
2
2
|
|
3
3
|
module JWTSignedRequest
|
4
4
|
class KeyStore
|
5
|
+
def self.find(id)
|
6
|
+
all[id]
|
7
|
+
end
|
8
|
+
|
9
|
+
private_class_method def self.all
|
10
|
+
@all ||= Hash.new { |result, key| result[key] = KeyStore.new }
|
11
|
+
end
|
12
|
+
|
5
13
|
def initialize
|
6
14
|
@signing_keys = {}
|
7
15
|
@verification_keys = {}
|
8
16
|
end
|
9
17
|
|
10
18
|
def add_signing_key(key_id:, key:, algorithm:)
|
11
|
-
@signing_keys.store(
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
19
|
+
@signing_keys.store(
|
20
|
+
key_id,
|
21
|
+
key: key,
|
22
|
+
algorithm: algorithm,
|
23
|
+
)
|
16
24
|
end
|
17
25
|
|
18
26
|
def add_verification_key(key_id:, key:, algorithm:)
|
19
|
-
@verification_keys.store(
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
27
|
+
@verification_keys.store(
|
28
|
+
key_id,
|
29
|
+
key: key,
|
30
|
+
algorithm: algorithm,
|
31
|
+
)
|
24
32
|
end
|
25
33
|
|
26
34
|
def get_signing_key(key_id:)
|
@@ -6,7 +6,8 @@ require 'jwt_signed_request'
|
|
6
6
|
module JWTSignedRequest
|
7
7
|
module Middlewares
|
8
8
|
class Faraday < Faraday::Middleware
|
9
|
-
def initialize(app, options)
|
9
|
+
def initialize(app, bearer_schema: nil, **options)
|
10
|
+
@bearer_schema = bearer_schema
|
10
11
|
@options = options
|
11
12
|
super(app)
|
12
13
|
end
|
@@ -19,7 +20,7 @@ module JWTSignedRequest
|
|
19
20
|
path: env[:url].request_uri,
|
20
21
|
headers: env[:request_headers],
|
21
22
|
body: env[:body],
|
22
|
-
**
|
23
|
+
**options,
|
23
24
|
)
|
24
25
|
|
25
26
|
env[:request_headers].store("Authorization", authorization_header)
|
@@ -29,24 +30,14 @@ module JWTSignedRequest
|
|
29
30
|
|
30
31
|
private
|
31
32
|
|
32
|
-
attr_reader :app, :env, :options, :jwt_token
|
33
|
+
attr_reader :app, :env, :bearer_schema, :options, :jwt_token
|
33
34
|
|
34
35
|
def authorization_header
|
35
36
|
bearer_schema? ? "Bearer #{jwt_token}" : jwt_token
|
36
37
|
end
|
37
38
|
|
38
39
|
def bearer_schema?
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
def optional_settings
|
43
|
-
{
|
44
|
-
secret_key: options[:secret_key],
|
45
|
-
algorithm: options[:algorithm],
|
46
|
-
additional_headers_to_sign: options[:additional_headers_to_sign],
|
47
|
-
key_id: options[:key_id],
|
48
|
-
issuer: options[:issuer],
|
49
|
-
}.reject { |_, value| value.nil? }
|
40
|
+
bearer_schema == true
|
50
41
|
end
|
51
42
|
end
|
52
43
|
end
|
@@ -8,41 +8,40 @@ module JWTSignedRequest
|
|
8
8
|
class Rack
|
9
9
|
UNAUTHORIZED_STATUS_CODE = 401
|
10
10
|
|
11
|
-
def initialize(app,
|
11
|
+
def initialize(app, secret_key: nil, algorithm: nil, leeway: nil, exclude_paths: nil, key_store_id: nil)
|
12
12
|
@app = app
|
13
|
-
@secret_key =
|
14
|
-
@algorithm =
|
15
|
-
@leeway =
|
16
|
-
@exclude_paths =
|
13
|
+
@secret_key = secret_key
|
14
|
+
@algorithm = algorithm
|
15
|
+
@leeway = leeway
|
16
|
+
@exclude_paths = exclude_paths
|
17
|
+
@key_store_id = key_store_id
|
17
18
|
end
|
18
19
|
|
19
20
|
def call(env)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
secret_key: secret_key,
|
25
|
-
algorithm: algorithm,
|
26
|
-
leeway: leeway
|
27
|
-
}.reject { |_, value| value.nil? }
|
28
|
-
|
29
|
-
::JWTSignedRequest.verify(**args)
|
30
|
-
end
|
31
|
-
|
32
|
-
app.call(env)
|
33
|
-
rescue ::JWTSignedRequest::UnauthorizedRequestError => e
|
34
|
-
[UNAUTHORIZED_STATUS_CODE, {'Content-Type' => 'application/json'} , []]
|
35
|
-
end
|
21
|
+
::JWTSignedRequest.verify(**verification_args(env)) unless excluded_path?(env)
|
22
|
+
app.call(env)
|
23
|
+
rescue ::JWTSignedRequest::UnauthorizedRequestError
|
24
|
+
[UNAUTHORIZED_STATUS_CODE, {'Content-Type' => 'application/json'}, []]
|
36
25
|
end
|
37
26
|
|
38
27
|
private
|
39
28
|
|
40
|
-
attr_reader :app, :secret_key, :algorithm, :leeway, :exclude_paths
|
29
|
+
attr_reader :app, :secret_key, :algorithm, :leeway, :exclude_paths, :key_store_id
|
41
30
|
|
42
31
|
def excluded_path?(env)
|
43
32
|
!exclude_paths.nil? &&
|
44
33
|
env['PATH_INFO'].match(exclude_paths)
|
45
34
|
end
|
35
|
+
|
36
|
+
def verification_args(env)
|
37
|
+
{
|
38
|
+
request: ::Rack::Request.new(env),
|
39
|
+
secret_key: secret_key,
|
40
|
+
algorithm: algorithm,
|
41
|
+
leeway: leeway,
|
42
|
+
key_store_id: key_store_id,
|
43
|
+
}
|
44
|
+
end
|
46
45
|
end
|
47
46
|
end
|
48
47
|
end
|
@@ -18,7 +18,8 @@ module JWTSignedRequest
|
|
18
18
|
key_id: nil,
|
19
19
|
lookup_key_id: key_id,
|
20
20
|
issuer: nil,
|
21
|
-
additional_headers_to_sign: nil
|
21
|
+
additional_headers_to_sign: nil,
|
22
|
+
key_store_id: nil
|
22
23
|
)
|
23
24
|
@method = method
|
24
25
|
@path = path
|
@@ -30,6 +31,7 @@ module JWTSignedRequest
|
|
30
31
|
@lookup_key_id = lookup_key_id
|
31
32
|
@issuer = issuer
|
32
33
|
@additional_headers_to_sign = additional_headers_to_sign
|
34
|
+
@key_store_id = key_store_id
|
33
35
|
end
|
34
36
|
|
35
37
|
def call
|
@@ -38,12 +40,24 @@ module JWTSignedRequest
|
|
38
40
|
|
39
41
|
private
|
40
42
|
|
41
|
-
attr_reader
|
42
|
-
:method,
|
43
|
-
:
|
43
|
+
attr_reader(
|
44
|
+
:method,
|
45
|
+
:path,
|
46
|
+
:body,
|
47
|
+
:headers,
|
48
|
+
:key_id,
|
49
|
+
:lookup_key_id,
|
50
|
+
:issuer,
|
51
|
+
:additional_headers_to_sign,
|
52
|
+
:key_store_id,
|
53
|
+
)
|
44
54
|
|
45
55
|
def stored_key
|
46
|
-
@stored_key ||=
|
56
|
+
@stored_key ||= key_store.get_signing_key(key_id: lookup_key_id)
|
57
|
+
end
|
58
|
+
|
59
|
+
def key_store
|
60
|
+
KeyStore.find(key_store_id)
|
47
61
|
end
|
48
62
|
|
49
63
|
def secret_key
|
@@ -11,12 +11,13 @@ module JWTSignedRequest
|
|
11
11
|
end
|
12
12
|
|
13
13
|
# TODO: secret_key & algorithm is deprecated and will be removed in future.
|
14
|
-
# For now we will support its
|
15
|
-
def initialize(request:, secret_key: nil, algorithm: nil, leeway: nil)
|
14
|
+
# For now we will support its functionality
|
15
|
+
def initialize(request:, secret_key: nil, algorithm: nil, leeway: nil, key_store_id: nil)
|
16
16
|
@request = request
|
17
17
|
@secret_key = secret_key
|
18
18
|
@algorithm = algorithm
|
19
19
|
@leeway = leeway
|
20
|
+
@key_store_id = key_store_id
|
20
21
|
end
|
21
22
|
|
22
23
|
def call
|
@@ -29,19 +30,23 @@ module JWTSignedRequest
|
|
29
30
|
|
30
31
|
private
|
31
32
|
|
32
|
-
attr_reader :request, :leeway
|
33
|
+
attr_reader :request, :leeway, :key_store_id
|
33
34
|
|
34
35
|
def stored_key
|
35
36
|
_body, jwt_header = ::JWT.decode(jwt_token, nil, false)
|
36
37
|
key_id = jwt_header.fetch('kid') { raise MissingKeyIdError }
|
37
38
|
signed_algorithm = jwt_header.fetch('alg')
|
38
|
-
|
39
|
+
key_store.get_verification_key(key_id: key_id).tap do |key|
|
39
40
|
if signed_algorithm != key[:algorithm]
|
40
41
|
raise AlgorithmMismatchError
|
41
42
|
end
|
42
43
|
end
|
43
44
|
end
|
44
45
|
|
46
|
+
def key_store
|
47
|
+
KeyStore.find(key_store_id)
|
48
|
+
end
|
49
|
+
|
45
50
|
def algorithm
|
46
51
|
@algorithm ||= stored_key.fetch(:algorithm) { raise MissingAlgorithmError }
|
47
52
|
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: 2.
|
4
|
+
version: 2.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Envato
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-05
|
11
|
+
date: 2020-08-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jwt
|
@@ -132,7 +132,7 @@ metadata:
|
|
132
132
|
bug_tracker_uri: https://github.com/envato/jwt_signed_request/issues
|
133
133
|
changelog_uri: https://github.com/envato/jwt_signed_request/blob/master/CHANGELOG.md
|
134
134
|
source_code_uri: https://github.com/envato/jwt_signed_request
|
135
|
-
post_install_message:
|
135
|
+
post_install_message:
|
136
136
|
rdoc_options: []
|
137
137
|
require_paths:
|
138
138
|
- lib
|
@@ -148,7 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
148
148
|
version: '0'
|
149
149
|
requirements: []
|
150
150
|
rubygems_version: 3.1.2
|
151
|
-
signing_key:
|
151
|
+
signing_key:
|
152
152
|
specification_version: 4
|
153
153
|
summary: JWT request signing and verification for Internal APIs
|
154
154
|
test_files: []
|