jwt_signed_request 2.5.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6a4b6d9d7869c58d436404dc533c1918dc21a8fb
4
- data.tar.gz: 4e87bacd334d5cafe414f6d350eb6b6c556fb2c1
2
+ SHA256:
3
+ metadata.gz: fb9e7a555755580bb4b37387799d44d3bc4d67d7a157060fce83183827250d15
4
+ data.tar.gz: b4ed152d2dfa73fc961280a6d7c09fd480565195c280f76b71e6326268ba894d
5
5
  SHA512:
6
- metadata.gz: ebea05eab2c08b118fa7fd01b329e8e48ad1385806a3d97ed7cb6c3642b4d58a7f19bbcf4234b5ad7c6aa87fd71abce1cec8f582144deaeff7f24edb8bcfa61e
7
- data.tar.gz: 4b23e201723c6066820df703c7bdf4c8fa281c281beef379f3ffb4c13fa333f95d54c348fae53224ae44222258858c1431fb94a94d9f8925c2d36930ec2cfb03
6
+ metadata.gz: 6d7f1c2fe8ffac7c069d11231ae797fb92967cece0ed2c441167d02ebec178edb6d9474d02a0e4ec3d9a4840d8df0888cb6b00029f99190e0b24f1fceaf2f0f2
7
+ data.tar.gz: 0b38896799b5021d54266010534f5360819d95363b7f09a276e7b729df8d708bacd9c861e1ae194e3b6f2bd7894e7f32f2e1e60d486d00b07b84705d2cbc4713
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
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 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,10 +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
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
+ )
112
151
 
113
152
  faraday.adapter Faraday.default_adapter
114
153
  end
@@ -119,10 +158,23 @@ conn.post do |req|
119
158
  end
120
159
  ```
121
160
 
122
- ## Verifying Requests
161
+ #### Additional options
162
+
163
+ ##### bearer_schema (boolean)
123
164
 
124
- 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.
165
+ Determines whether to use the [Bearer schema](https://auth0.com/docs/jwt#how-do-json-web-tokens-work-) when assigning the JWT token to the `Authorization` request header
125
166
 
167
+ | bearer_schema value | Authorization header value|
168
+ |---------------------|---------------------------|
169
+ | false (default) | `<jwt_token>` |
170
+ | true | `Bearer <jwt_token>` |
171
+
172
+
173
+ ## Verifying Requests
174
+
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.
126
178
 
127
179
  ## Using Rails
128
180
 
@@ -136,7 +188,11 @@ class APIController < ApplicationController
136
188
 
137
189
  def verify_request
138
190
  begin
139
- 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
+ )
140
196
 
141
197
  rescue JWTSignedRequest::UnauthorizedRequestError => e
142
198
  render :json => {}, :status => :unauthorized
@@ -158,9 +214,12 @@ JWT tokens contain an expiry timestamp. If communication delays are large (or sy
158
214
 
159
215
  ```ruby
160
216
  class Server < Sinatra::Base
161
- use JWTSignedRequest::Middlewares::Rack,
162
- exclude_paths: /public|health/, # optional regex
163
- 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
+ )
164
223
  end
165
224
  ```
166
225
 
@@ -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,36 +6,38 @@ 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
13
14
 
14
15
  def call(env)
15
- jwt_token = ::JWTSignedRequest.sign(
16
+ env[:body] ||= ::JWTSignedRequest::EMPTY_BODY
17
+
18
+ @jwt_token = ::JWTSignedRequest.sign(
16
19
  method: env[:method],
17
20
  path: env[:url].request_uri,
18
21
  headers: env[:request_headers],
19
- body: env.fetch(:body, ::JWTSignedRequest::EMPTY_BODY),
20
- **optional_settings
22
+ body: env[:body],
23
+ **options,
21
24
  )
22
25
 
23
- env[:request_headers].store("Authorization", "Bearer #{jwt_token}")
26
+ env[:request_headers].store("Authorization", authorization_header)
27
+
24
28
  app.call(env)
25
29
  end
26
30
 
27
31
  private
28
32
 
29
- attr_reader :app, :env, :options
33
+ attr_reader :app, :env, :bearer_schema, :options, :jwt_token
34
+
35
+ def authorization_header
36
+ bearer_schema? ? "Bearer #{jwt_token}" : jwt_token
37
+ end
30
38
 
31
- def optional_settings
32
- {
33
- secret_key: options[:secret_key],
34
- algorithm: options[:algorithm],
35
- additional_headers_to_sign: options[:additional_headers_to_sign],
36
- key_id: options[:key_id],
37
- issuer: options[:issuer],
38
- }.reject { |_, value| value.nil? }
39
+ def bearer_schema?
40
+ bearer_schema == true
39
41
  end
40
42
  end
41
43
  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.0'.freeze
4
+ VERSION = '2.6.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.0
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: 2019-01-22 00:00:00.000000000 Z
11
+ date: 2020-08-05 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
@@ -138,7 +132,7 @@ metadata:
138
132
  bug_tracker_uri: https://github.com/envato/jwt_signed_request/issues
139
133
  changelog_uri: https://github.com/envato/jwt_signed_request/blob/master/CHANGELOG.md
140
134
  source_code_uri: https://github.com/envato/jwt_signed_request
141
- post_install_message:
135
+ post_install_message:
142
136
  rdoc_options: []
143
137
  require_paths:
144
138
  - lib
@@ -153,9 +147,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
147
  - !ruby/object:Gem::Version
154
148
  version: '0'
155
149
  requirements: []
156
- rubyforge_project:
157
- rubygems_version: 2.6.13
158
- signing_key:
150
+ rubygems_version: 3.1.2
151
+ signing_key:
159
152
  specification_version: 4
160
153
  summary: JWT request signing and verification for Internal APIs
161
154
  test_files: []