warden-jwt_auth 0.8.0 → 0.12.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
2
  SHA256:
3
- metadata.gz: d1170f1a68adb34c8769b7c202ba91ca706494562205ef9b8ab56956f0d66393
4
- data.tar.gz: 9334625e9e7711c76a90c6d22414871402171e317f9825cc3d6e4d3e5ca486fd
3
+ metadata.gz: 8cdd3cda879ac9ddadab8420efc9be8bd619ab77542fb77c4b2f6e12c3712da8
4
+ data.tar.gz: 3d7d716491fc3884503addf8de20efda5a58a005db6eee3b992c9ccde5b4338c
5
5
  SHA512:
6
- metadata.gz: 6ab3bbcd295d3206006878d307006a7033be2bfa94237f45e607979b8217d5b84222f076700e1893633b9d716b159d5d576183ca9a261a36c0b76f341c8f1d22
7
- data.tar.gz: 95619a1fee6709a8aa7eb883af56abf1e7540acde7b3eb690a7b6da142f203cd41bc222af6cac12937ac18f909a5974437cedf7d76f32262f4f6cca275ada89b
6
+ metadata.gz: f90a107cc3cd30099f1505a15c93e1c4bb8b30df2bbcd069bea112a424cf2ca1e3a37b46da76a78fb48c6a4dc2e313ad03f3511981839bb141e4ae43199c622a
7
+ data.tar.gz: 2594477d2bef3bf246f9c5da680278ec7f5f1c178bb4f7e431a28cf4feb352e043fa096132050f4211886bb5ec08e27c5ef88aeea5101304e00a59f58e900a53
@@ -0,0 +1,21 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ ruby-version: ['3.1', '3.2', '3.3', '3.4', ruby-head]
11
+
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - name: Set up Ruby ${{ matrix.ruby-version }}
15
+ uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: ${{ matrix.ruby-version }}
18
+ bundler-cache: true # 'bundle install' and cache
19
+ - name: Run specs
20
+ run: |
21
+ bundle exec rspec
@@ -0,0 +1,17 @@
1
+ name: Lint
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ lint:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v4
10
+ - name: Set up Ruby ${{ matrix.ruby-version }}
11
+ uses: ruby/setup-ruby@v1
12
+ with:
13
+ ruby-version: 2.7
14
+ bundler-cache: true # 'bundle install' and cache
15
+ - name: Run specs
16
+ run: |
17
+ bundle exec rubocop
data/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
+ ## [0.12.0] - 2024-09-14
8
+ - Support jwt v3 ([65](https://github.com/waiting-for-dev/warden-jwt_auth/pull/65))
9
+
10
+ ## [0.11.0] - 2024-12-20
11
+ - Prevent strategy from running when the current path matches a dispatch request ([60](https://github.com/waiting-for-dev/warden-jwt_auth/pull/60))
12
+
13
+ ## [0.10.1] - 2024-12-15
14
+ - Fix version mismatch
15
+
16
+ ## [0.8.0] - 2024-06-28
17
+ - Add support for issue claim ([56](https://github.com/waiting-for-dev/warden-jwt_auth/pull/56))
18
+
7
19
  ## [0.8.0] - 2023-01-31
8
20
  - Add support for secret rotation ([49](https://github.com/waiting-for-dev/warden-jwt_auth/pull/49))
9
21
  - Support dry-* v1 ([52](https://github.com/waiting-for-dev/warden-jwt_auth/pull/52))
data/README.md CHANGED
@@ -145,7 +145,7 @@ config.dispatch_requests = [
145
145
 
146
146
  **Important**: You are encouraged to delimit your regular expression with `^` and `$` to avoid unintentional matches.
147
147
 
148
- Tokens will be returned in the `Authorization` response header, with format `Bearer #{token}`.
148
+ Tokens will be returned in the `Authorization` response header (configurable via `config.token_header`), with format `Bearer #{token}`.
149
149
 
150
150
  ### Requests authentication
151
151
 
@@ -175,14 +175,14 @@ config.revocation_strategies = { user: RevocationStrategy }
175
175
 
176
176
  The implementation of the revocation strategy is also on your side. They just need to implement two methods: `jwt_revoked?` and `revoke_jwt`, both of them accepting as parameters the JWT payload and the user record, in this order.
177
177
 
178
- You can read about which [JWT recovation strategies](http://waiting-for-dev.github.io/blog/2017/01/24/jwt_revocation_strategies/) can be implement with their pros and cons.
178
+ You can read about which [JWT recovation strategies](http://waiting-for-dev.github.io/blog/2017/01/24/jwt_revocation_strategies) can be implement with their pros and cons.
179
179
 
180
180
  ```ruby
181
181
  module RevocationStrategy
182
182
  def self.jwt_revoked?(payload, user)
183
183
  # Does something to check whether the JWT token is revoked for given user
184
184
  end
185
-
185
+
186
186
  def self.revoke_jwt(payload, user)
187
187
  # Does something to revoke the JWT token for given user
188
188
  end
@@ -208,6 +208,17 @@ end
208
208
 
209
209
  You can remove the `rotation_secret` when you are condifent that large enough user base has the fetched the token encrypted with the new secret.
210
210
 
211
+ ### Multiple issuers
212
+
213
+ When your application handles JWT tokens from multiple sources (e.g. webhooks authenticated via provider JTW tokens) you can configure this gem to use the [issuer claim](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1) to only handle tokens it has issued.
214
+
215
+ ```ruby
216
+ Warden::JWTAuth.configure do |config|
217
+ config.secret = ENV['WARDEN_JWT_SECRET_KEY']
218
+ config.issuer = 'http://my-application.com'
219
+ end
220
+ ```
221
+
211
222
  ## Development
212
223
 
213
224
  There are docker and docker-compose files configured to create a development environment for this gem. So, if you use Docker you only need to run:
@@ -25,16 +25,17 @@ module Warden
25
25
  env['REQUEST_METHOD']
26
26
  end
27
27
 
28
- # Returns HTTP_AUTHORIZATION environment variable
28
+ # Returns header configured through `token_header` option
29
29
  #
30
30
  # @param env [Hash] Rack env
31
31
  # @return [String]
32
32
  def self.authorization_header(env)
33
- env['HTTP_AUTHORIZATION']
33
+ header_env_name = env_name(JWTAuth.config.token_header)
34
+ env[header_env_name]
34
35
  end
35
36
 
36
- # Returns a copy of `env` with value added to the `HTTP_AUTHORIZATION`
37
- # environment variable.
37
+ # Returns a copy of `env` with value added to the environment variable
38
+ # configured through `token_header` option
38
39
  #
39
40
  # Be aware than `env` is not modified in place and still an updated copy
40
41
  # is returned.
@@ -44,7 +45,8 @@ module Warden
44
45
  # @return [Hash] modified rack env
45
46
  def self.set_authorization_header(env, value)
46
47
  env = env.dup
47
- env['HTTP_AUTHORIZATION'] = value
48
+ header_env_name = env_name(JWTAuth.config.token_header)
49
+ env[header_env_name] = value
48
50
  env
49
51
  end
50
52
 
@@ -53,8 +55,16 @@ module Warden
53
55
  # @param env [Hash] Rack env
54
56
  # @return [String]
55
57
  def self.aud_header(env)
56
- env_name = ('HTTP_' + JWTAuth.config.aud_header.upcase).tr('-', '_')
57
- env[env_name]
58
+ header_env_name = env_name(JWTAuth.config.aud_header)
59
+ env[header_env_name]
60
+ end
61
+
62
+ # Returns the ENV name for a given header
63
+ #
64
+ # @param header [String] Header name
65
+ # @return [String]
66
+ def self.env_name(header)
67
+ ('HTTP_' + header.upcase).tr('-', '_')
58
68
  end
59
69
  end
60
70
  end
@@ -21,8 +21,8 @@ module Warden
21
21
  method == METHOD ? token : nil
22
22
  end
23
23
 
24
- # Returns a copy of `env` with token added to the `HTTP_AUTHORIZATION`
25
- # header. Be aware than `env` is not modified in place.
24
+ # Returns a copy of `env` with token added to the header configured through
25
+ # `token_header` option. Be aware than `env` is not modified in place.
26
26
  #
27
27
  # @param env [Hash] rack env hash
28
28
  # @param token [String] JWT token
@@ -39,7 +39,7 @@ module Warden
39
39
  # @return [Hash] response headers with the token added
40
40
  def self.to_headers(headers, token)
41
41
  headers = headers.dup
42
- headers['Authorization'] = "#{METHOD} #{token}"
42
+ headers[JWTAuth.config.token_header] = "#{METHOD} #{token}"
43
43
  headers
44
44
  end
45
45
  end
@@ -47,8 +47,7 @@ module Warden
47
47
  end
48
48
 
49
49
  def request_matches?(path_info, method)
50
- dispatch_requests.each do |tuple|
51
- dispatch_method, dispatch_path = tuple
50
+ dispatch_requests.each do |(dispatch_method, dispatch_path)|
52
51
  return true if path_info.match(dispatch_path) &&
53
52
  method == dispatch_method
54
53
  end
@@ -29,6 +29,14 @@ module Warden
29
29
  payload['aud'] == aud
30
30
  end
31
31
 
32
+ # Returns whether given issuer matches with the one encoded in the payload
33
+ # @param payload [Hash] JWT payload
34
+ # @param issuer [String] The issuer to match
35
+ # @return [Boolean]
36
+ def self.issuer_matches?(payload, issuer)
37
+ payload['iss'] == issuer.to_s
38
+ end
39
+
32
40
  # Returns the payload to encode for a given user in a scope
33
41
  #
34
42
  # @param user [Interfaces::User] an user, whatever it is
@@ -7,8 +7,10 @@ module Warden
7
7
  # Warden strategy to authenticate an user through a JWT token in the
8
8
  # `Authorization` request header
9
9
  class Strategy < Warden::Strategies::Base
10
+ include JWTAuth::Import['dispatch_requests']
11
+
10
12
  def valid?
11
- !token.nil?
13
+ token_exists? && issuer_claim_valid? && !path_is_dispatch_request_path?
12
14
  end
13
15
 
14
16
  def store?
@@ -25,6 +27,28 @@ module Warden
25
27
 
26
28
  private
27
29
 
30
+ def path_is_dispatch_request_path?
31
+ current_path = EnvHelper.path_info(env)
32
+ request_method = EnvHelper.request_method(env)
33
+ dispatch_requests.any? do |(dispatch_method, dispatch_path)|
34
+ request_method == dispatch_method && current_path.match(dispatch_path)
35
+ end
36
+ end
37
+
38
+ def issuer_claim_valid?
39
+ configured_issuer = Warden::JWTAuth.config.issuer
40
+ return true if configured_issuer.nil?
41
+
42
+ payload = TokenDecoder.new.call(token)
43
+ PayloadUserHelper.issuer_matches?(payload, configured_issuer)
44
+ rescue JWT::DecodeError
45
+ true
46
+ end
47
+
48
+ def token_exists?
49
+ !token.nil?
50
+ end
51
+
28
52
  def token
29
53
  @token ||= HeaderParser.from_env(env)
30
54
  end
@@ -7,7 +7,7 @@ module Warden
7
7
  # Encodes a payload into a JWT token, adding some configurable
8
8
  # claims
9
9
  class TokenEncoder
10
- include JWTAuth::Import['secret', 'algorithm', 'expiration_time']
10
+ include JWTAuth::Import['secret', 'algorithm', 'expiration_time', 'issuer']
11
11
 
12
12
  # Encodes a payload into a JWT
13
13
  #
@@ -24,6 +24,7 @@ module Warden
24
24
  now = Time.now.to_i
25
25
  payload['iat'] ||= now
26
26
  payload['exp'] ||= now + expiration_time
27
+ payload['iss'] ||= issuer if issuer
27
28
  payload['jti'] ||= SecureRandom.uuid
28
29
  payload
29
30
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Warden
4
4
  module JWTAuth
5
- VERSION = '0.8.0'
5
+ VERSION = '0.12.0'
6
6
  end
7
7
  end
@@ -19,6 +19,8 @@ module Warden
19
19
  module JWTAuth
20
20
  extend Dry::Configurable
21
21
 
22
+ module_function
23
+
22
24
  def symbolize_keys(hash)
23
25
  hash.transform_keys(&:to_sym)
24
26
  end
@@ -36,8 +38,6 @@ module Warden
36
38
  end
37
39
  end
38
40
 
39
- module_function :constantize_values, :symbolize_keys, :upcase_first_items
40
-
41
41
  # The secret used to encode the token
42
42
  setting :secret
43
43
 
@@ -53,6 +53,16 @@ module Warden
53
53
  # Expiration time for tokens
54
54
  setting :expiration_time, default: 3600
55
55
 
56
+ # Request header that will be used for receiving and returning the token.
57
+ setting :token_header, default: 'Authorization'
58
+
59
+ # The issuer claims associated with the tokens
60
+ #
61
+ # Will be used to only apply the warden strategy when the issuer matches.
62
+ # This allows for multiple token issuers being used.
63
+ # @see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1
64
+ setting :issuer, default: nil
65
+
56
66
  # Request header which value will be encoded as `aud` claim in JWT. If
57
67
  # the header is not present `aud` will be `nil`.
58
68
  setting :aud_header, default: 'JWT_AUD'
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.add_dependency 'dry-auto_inject', '>= 0.8', '< 2'
26
26
  spec.add_dependency 'dry-configurable', '>= 0.13', '< 2'
27
- spec.add_dependency 'jwt', '~> 2.1'
27
+ spec.add_dependency 'jwt', '>= 2.1', '< 4'
28
28
  spec.add_dependency 'warden', '~> 1.2'
29
29
 
30
30
  spec.add_development_dependency 'bundler'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: warden-jwt_auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc Busqué
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-01-31 00:00:00.000000000 Z
11
+ date: 2025-09-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-auto_inject
@@ -54,16 +54,22 @@ dependencies:
54
54
  name: jwt
55
55
  requirement: !ruby/object:Gem::Requirement
56
56
  requirements:
57
- - - "~>"
57
+ - - ">="
58
58
  - !ruby/object:Gem::Version
59
59
  version: '2.1'
60
+ - - "<"
61
+ - !ruby/object:Gem::Version
62
+ version: '4'
60
63
  type: :runtime
61
64
  prerelease: false
62
65
  version_requirements: !ruby/object:Gem::Requirement
63
66
  requirements:
64
- - - "~>"
67
+ - - ">="
65
68
  - !ruby/object:Gem::Version
66
69
  version: '2.1'
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: '4'
67
73
  - !ruby/object:Gem::Dependency
68
74
  name: warden
69
75
  requirement: !ruby/object:Gem::Requirement
@@ -214,10 +220,11 @@ extra_rdoc_files: []
214
220
  files:
215
221
  - ".codeclimate.yml"
216
222
  - ".github/FUNDING.yml"
223
+ - ".github/workflows/ci.yml"
224
+ - ".github/workflows/lint.yml"
217
225
  - ".gitignore"
218
226
  - ".rspec"
219
227
  - ".rubocop.yml"
220
- - ".travis.yml"
221
228
  - CHANGELOG.md
222
229
  - CODE_OF_CONDUCT.md
223
230
  - Dockerfile
@@ -252,7 +259,7 @@ licenses:
252
259
  - MIT
253
260
  metadata:
254
261
  rubygems_mfa_required: 'true'
255
- post_install_message:
262
+ post_install_message:
256
263
  rdoc_options: []
257
264
  require_paths:
258
265
  - lib
@@ -267,8 +274,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
267
274
  - !ruby/object:Gem::Version
268
275
  version: '0'
269
276
  requirements: []
270
- rubygems_version: 3.0.3.1
271
- signing_key:
277
+ rubygems_version: 3.5.9
278
+ signing_key:
272
279
  specification_version: 4
273
280
  summary: JWT authentication for Warden.
274
281
  test_files: []
data/.travis.yml DELETED
@@ -1,21 +0,0 @@
1
- language: ruby
2
- cache: bundler
3
- rvm:
4
- - 2.6
5
- - 2.7
6
- - 3.0
7
- - ruby-head
8
- before_install:
9
- - gem update --system --no-doc
10
- - gem install bundler
11
- script:
12
- - bundle exec rspec
13
- - bundle exec rubocop
14
- - bundle exec codeclimate-test-reporter
15
- jobs:
16
- allow_failures:
17
- - rvm: ruby-head
18
- addons:
19
- code_climate:
20
- repo_token:
21
- secure: neJ5LVLV6vgeCnerSQjUpLuQDvxEH87iW8swCSWl2hTtPcD/GuwYSeSXnhH72HVHi/9basHHhaYPcE2YeBwBCr39PhiHMNwS5GGGk/RGjKpU/Gt1KXV8KXTbNGT4v+ZMM3cdsdDfe8OnzGguNVsdxseHa3KE2pyuvo2a0swXwKa7BU9VB/3ZoZvvfI3Xr9im4eklWam5yCwVR0FOF7epzmNTKMXcUga2BOc9PV5aVELzLILLCHCJSCupe5Rx8mfcsRoRmZXKduF8Ke3eq8eULvLEo4EGfC107najOqrKt7x8uDVIsuGrP4LUQ4ainmNEb2jIvpjuqAxpusMjhpjCINF1Tn0OXK93OXAp4QKeIYoYEqKtzRxX0TWFNWHB8ombF9HTMF2DmloDZyFRiI40JSMImU0hc4MDxRgiTW5MDWGbohDaJ+9VV6+rIqtlEfLhgj1grFBAroaJce9BB7RQEmfsZPzhC2VXwGxHw/YkJgzBNGq1/9E1DoTY9RPSNTQfSRodhI3XW8LSQSHTBeXZvymVcjeOyYgjzJYviLHR8QS4cXpUALtlFXyaMkPHUBLUn8XsBa5Azfh5y3qPMGiJq1/qaHA4mKj5ls+ngFbzOq82sYGAKgQHj/ZDb+FZMQQanp4jyWADKcpXcmINb9jEQwkU0bjpuhUYtghASxH1Kl8=