jwt_signed_request 2.5.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 79cbd2415ebe2ccd5475a7dcce55816100def17b35e7e74b0f30e41062969f35
4
- data.tar.gz: 8ca7449ac266884163f7cf73b9f89aebc463099ecc3193619b5199f5d1b185a4
3
+ metadata.gz: a232713f444aa93fe62190f100a0ee0b74550c5f8426095c131b79ec035c1260
4
+ data.tar.gz: df9755b248452d267d2a0631ea16af0d81476a501dde8367b980fe53e6b9c210
5
5
  SHA512:
6
- metadata.gz: bd580744d0f274948091f1424e34289c01c95a66027434dbbdc2eccdf7bc7a4aa0905ad19dc2574b86640f7035040a790b6bfb36d0006ecda156664e0498e48a
7
- data.tar.gz: e607c1345c5293afb153bbc173590a785b4472366ba383d984be39de0c2f8ca6c64a19ce9627b9df54865d6988e6ac2e7c1c33ef91a5911bf7189065e0ffb2a1
6
+ metadata.gz: 6e8ca9735e699cb864d1a73869d8b09cfda8e8decebae2b7343bd2168f1f4fe2baed43c02ee52e7d76c761de3af66aacdf69667805a097d14ae04f4896db238a
7
+ data.tar.gz: ffb84c36f824f1e8d4ad22718136fa4350ea56a28e842c098f7038abd9c31e3c3b1e168f0546ed281018cd54c91abfbd24add657cf2f705ba230db87f89ab17c
data/README.md CHANGED
@@ -19,7 +19,7 @@ $ bundle
19
19
 
20
20
  ## Generating EC Keys
21
21
 
22
- We should be using a public key encryption alogorithm such as **ES256**. To generate your public/private key pair using **ES256** run:
22
+ We should be using a public key encryption algorithm such as **ES256**. To generate your public/private key pair using **ES256** run:
23
23
 
24
24
  ```sh
25
25
  $ openssl ecparam -genkey -name prime256v1 -noout -out myprivatekey.pem
@@ -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 the key store as your application needs them.
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 = <<-pem.gsub(/^\s+/, "")
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
- pem
44
+ PEM
43
45
 
44
- public_key = <<-pem.gsub(/^\s+/, "")
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
- pem
51
+ PEM
52
+ ```
53
+
54
+ ### Single key store
50
55
 
56
+ If your application only needs a single key store, configure it like so:
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 the key store, you will only need to specify the `key_id` you are signing the requests with.
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 faraday
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 JWTSignedRequest::Middlewares::Faraday,
109
- key_id: 'my-key-id',
110
- issuer: 'my-issuer', # optional
111
- additional_headers_to_sign: ['X-AUTH'], # optional
112
- bearer_schema: true # optional
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 store. Doing so will allow the server to verify requests signed by different signing keys.
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(request: request)
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 JWTSignedRequest::Middlewares::Rack,
175
- exclude_paths: /public|health/, # optional regex
176
- leeway: 100 # optional
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
 
@@ -223,3 +269,33 @@ For bug fixes, documentation changes, and small features:
223
269
  5. Create a new Pull Request
224
270
 
225
271
  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
272
+
273
+ ### Compatibility
274
+
275
+ Compatibility with multiple versions of the [JWT gem] is tested via the [appraisal gem].
276
+
277
+ Configured versions are defined in [Appraisals](./Appraisals), which at time of writing looked like this:
278
+
279
+ ```ruby
280
+ # Latest JWT minor versions
281
+ # Source: https://rubygems.org/gems/jwt/versions
282
+ %w[
283
+ 1.5.6
284
+ 2.0.0
285
+ 2.1.0
286
+ 2.2.1
287
+ ].each do |jwt_version|
288
+ ```
289
+
290
+ Run the test suite like this:
291
+
292
+ ```sh
293
+ # Test all configured versions
294
+ bundle exec appraisal rspec
295
+
296
+ # Target a specific configured version
297
+ bundle exec appraisal jwt-1.5.6 rspec
298
+ ```
299
+
300
+ [JWT gem]: https://github.com/jwt/ruby-jwt
301
+ [appraisal gem]: https://github.com/thoughtbot/appraisal
@@ -9,22 +9,22 @@ require 'jwt_signed_request/errors'
9
9
  module JWTSignedRequest
10
10
  extend self
11
11
 
12
- DEFAULT_ALGORITHM = 'ES256'.freeze
13
- EMPTY_BODY = "".freeze
12
+ DEFAULT_ALGORITHM = 'ES256'
13
+ EMPTY_BODY = ''
14
14
 
15
- def configure_keys
16
- yield(key_store)
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
- @key_store ||= KeyStore.new
19
+ def key_store(id = nil)
20
+ KeyStore.find(id)
21
21
  end
22
22
 
23
- def sign(*args)
24
- Sign.call(*args)
23
+ def sign(**args)
24
+ Sign.call(**args)
25
25
  end
26
26
 
27
- def verify(*args)
28
- Verify.call(*args)
27
+ def verify(**args)
28
+ Verify.call(**args)
29
29
  end
30
30
  end
@@ -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(key_id,
12
- {
13
- key: key,
14
- algorithm: algorithm
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(key_id,
20
- {
21
- key: key,
22
- algorithm: algorithm
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,18 +6,22 @@ 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
- super(app)
12
+
13
+ initializer_args_requires_options? ? super(app, options) : super(app)
12
14
  end
13
15
 
14
16
  def call(env)
17
+ env[:body] ||= ::JWTSignedRequest::EMPTY_BODY
18
+
15
19
  @jwt_token = ::JWTSignedRequest.sign(
16
20
  method: env[:method],
17
21
  path: env[:url].request_uri,
18
22
  headers: env[:request_headers],
19
- body: env.fetch(:body, ::JWTSignedRequest::EMPTY_BODY),
20
- **optional_settings
23
+ body: env[:body],
24
+ **options,
21
25
  )
22
26
 
23
27
  env[:request_headers].store("Authorization", authorization_header)
@@ -27,24 +31,18 @@ module JWTSignedRequest
27
31
 
28
32
  private
29
33
 
30
- attr_reader :app, :env, :options, :jwt_token
34
+ attr_reader :app, :env, :bearer_schema, :options, :jwt_token
31
35
 
32
36
  def authorization_header
33
37
  bearer_schema? ? "Bearer #{jwt_token}" : jwt_token
34
38
  end
35
39
 
36
40
  def bearer_schema?
37
- options[:bearer_schema] == true
41
+ bearer_schema == true
38
42
  end
39
43
 
40
- def optional_settings
41
- {
42
- secret_key: options[:secret_key],
43
- algorithm: options[:algorithm],
44
- additional_headers_to_sign: options[:additional_headers_to_sign],
45
- key_id: options[:key_id],
46
- issuer: options[:issuer],
47
- }.reject { |_, value| value.nil? }
44
+ def initializer_args_requires_options?
45
+ Gem::Version.new(::Faraday::VERSION) >= Gem::Version.new('1.2.0')
48
46
  end
49
47
  end
50
48
  end
@@ -8,41 +8,40 @@ module JWTSignedRequest
8
8
  class Rack
9
9
  UNAUTHORIZED_STATUS_CODE = 401
10
10
 
11
- def initialize(app, options = {})
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 = options[:secret_key]
14
- @algorithm = options[:algorithm]
15
- @leeway = options[:leeway]
16
- @exclude_paths = options[: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
- begin
21
- unless excluded_path?(env)
22
- args = {
23
- request: ::Rack::Request.new(env),
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
@@ -4,8 +4,8 @@ require 'jwt_signed_request/claims'
4
4
 
5
5
  module JWTSignedRequest
6
6
  class Sign
7
- def self.call(*args)
8
- new(*args).call
7
+ def self.call(**args)
8
+ new(**args).call
9
9
  end
10
10
 
11
11
  def initialize(
@@ -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, :path, :body, :headers,
43
- :key_id, :lookup_key_id, :issuer, :additional_headers_to_sign
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 ||= JWTSignedRequest.key_store.get_signing_key(key_id: lookup_key_id)
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
@@ -6,17 +6,18 @@ require 'jwt/version'
6
6
 
7
7
  module JWTSignedRequest
8
8
  class Verify
9
- def self.call(*args)
10
- new(*args).call
9
+ def self.call(**args)
10
+ new(**args).call
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 functionaility
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
- JWTSignedRequest.key_store.get_verification_key(key_id: key_id).tap do |key|
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWTSignedRequest
4
- VERSION = '2.5.1'.freeze
4
+ VERSION = '3.0.0'
5
5
  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.5.1
4
+ version: 3.0.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: 2019-01-30 00:00:00.000000000 Z
11
+ date: 2021-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jwt
@@ -17,9 +17,6 @@ dependencies:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 1.5.0
20
- - - "<"
21
- - !ruby/object:Gem::Version
22
- version: 2.2.0
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
@@ -27,9 +24,6 @@ dependencies:
27
24
  - - ">="
28
25
  - !ruby/object:Gem::Version
29
26
  version: 1.5.0
30
- - - "<"
31
- - !ruby/object:Gem::Version
32
- version: 2.2.0
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: rack
35
29
  requirement: !ruby/object:Gem::Requirement
@@ -45,7 +39,7 @@ dependencies:
45
39
  - !ruby/object:Gem::Version
46
40
  version: '0'
47
41
  - !ruby/object:Gem::Dependency
48
- name: bundler
42
+ name: appraisal
49
43
  requirement: !ruby/object:Gem::Requirement
50
44
  requirements:
51
45
  - - ">="
@@ -59,7 +53,21 @@ dependencies:
59
53
  - !ruby/object:Gem::Version
60
54
  version: '0'
61
55
  - !ruby/object:Gem::Dependency
62
- name: rake
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '2'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack-test
63
71
  requirement: !ruby/object:Gem::Requirement
64
72
  requirements:
65
73
  - - ">="
@@ -73,7 +81,7 @@ dependencies:
73
81
  - !ruby/object:Gem::Version
74
82
  version: '0'
75
83
  - !ruby/object:Gem::Dependency
76
- name: rack-test
84
+ name: rake
77
85
  requirement: !ruby/object:Gem::Requirement
78
86
  requirements:
79
87
  - - ">="
@@ -138,7 +146,7 @@ metadata:
138
146
  bug_tracker_uri: https://github.com/envato/jwt_signed_request/issues
139
147
  changelog_uri: https://github.com/envato/jwt_signed_request/blob/master/CHANGELOG.md
140
148
  source_code_uri: https://github.com/envato/jwt_signed_request
141
- post_install_message:
149
+ post_install_message:
142
150
  rdoc_options: []
143
151
  require_paths:
144
152
  - lib
@@ -153,9 +161,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
161
  - !ruby/object:Gem::Version
154
162
  version: '0'
155
163
  requirements: []
156
- rubyforge_project:
157
- rubygems_version: 2.7.6
158
- signing_key:
164
+ rubygems_version: 3.1.2
165
+ signing_key:
159
166
  specification_version: 4
160
167
  summary: JWT request signing and verification for Internal APIs
161
168
  test_files: []