verikloak-audience 0.2.8 → 0.3.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: 88a1386b46aebb2940ede856bdde5993bd68a05000d71f9f5784d73dd85c2fc6
4
- data.tar.gz: cf84cfe50d3a6acf004ce0535b02b1644693d2c89b78e422d809b0f184e53cc3
3
+ metadata.gz: d97d79e3f6ccd8f2c920cc7dcb66612e9edbd577ba4be2f5948eddd16145dd9b
4
+ data.tar.gz: 4517ee945923ec595fa7447f33dd0e9370cc72a2a561bc04d4f910efed018595
5
5
  SHA512:
6
- metadata.gz: c97758819fac422b4f81e5d22655b677f7d1bd762430e8fd68d038b1fdf9adce004da3261b6de5d2af66874a9dc2a417a1c1272933d51acd979e55416131e4dc
7
- data.tar.gz: b12d1fa1f74f5b4d1bb17e461956ecabf4e094eb673b9a03e47bb174603ebcacf3d6bc3a48bab9854df8c3d94b3f6a7d252080f83248ca841354465554191740
6
+ metadata.gz: 83c14846297272179ccf6f4480dab677c82be3d21b32a6c374c3b40fc5be04dd0cf9c9c439457e9dd33d4b4a9fca393aca31b7104b4282479b872ad8987c8632
7
+ data.tar.gz: e2e125eab982f983e6447166bcd6b9d9413f15573301335f15ef53ba0bafb6d07020848ec942e9b532db69b29ab3fc6280ae9fc6f74d12ff9786a39fc64dccde
data/CHANGELOG.md CHANGED
@@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.3.0] - 2026-01-01
11
+
12
+ ### Fixed
13
+ - **Rails 8.x+ middleware insertion**: Fixed automatic middleware insertion not working in Rails 8.x+ due to queued middleware operations not being visible via `include?` checks.
14
+
15
+ ### Changed
16
+ - Railtie initializer now specifies `before: :build_middleware_stack` to ensure middleware insertion is queued before stack construction.
17
+ - Middleware insertion now uses `app.config.middleware` instead of `app.middleware` for more reliable queuing in Rails 8.x+.
18
+ - Extracted `middleware_not_found_error?` method to handle error detection across Rails versions (Rails 7's `MiddlewareNotFound` and Rails 8+'s `RuntimeError`).
19
+
20
+ ### Added
21
+ - `@middleware_insertion_attempted` class variable to prevent duplicate middleware insertion when both railtie and generator initializer run.
22
+ - `reset_middleware_insertion_flag!` method for testing purposes.
23
+ - Support for alternative error message patterns (`"does not exist"`, `/middleware.*not found/i`) for future Rails version compatibility.
24
+ - Comprehensive test coverage for:
25
+ - `include?` early return when middleware is already present
26
+ - Duplicate insertion prevention via flag
27
+ - Rails 7 `MiddlewareNotFound` exception handling
28
+ - Alternative error message patterns
29
+ - Unexpected exception re-raising
30
+
31
+ ## [0.2.9] - 2025-12-31
32
+
33
+ ### Added
34
+ - **Keycloak Integration** documentation section in README:
35
+ - Default audience behavior explanation (Access Token: `"account"`, ID Token: Client ID)
36
+ - Step-by-step guide for configuring Audience Mapper in Keycloak
37
+ - Configuration examples using `:allow_account` profile and array audiences
38
+ - Troubleshooting guide with JWT decoding commands
39
+
40
+ ### Documentation
41
+ - Added guidance for common Keycloak + audience validation issues
42
+ - Clarified the relationship between Keycloak client configuration and token `aud` claims
43
+
10
44
  ## [0.2.8] - 2025-09-28
11
45
 
12
46
  ### Changed
data/README.md CHANGED
@@ -71,6 +71,103 @@ See [`examples/rack.ru`](examples/rack.ru) for a full Rack sample. In Rails, alw
71
71
  - When audience validation fails, the middleware consults `env['verikloak.logger']`, `env['rack.logger']`, and `env['action_dispatch.logger']` (in that order) before falling back to Ruby's `Kernel#warn`, keeping failure logs consistent with Rails and Verikloak observers.
72
72
  - For the `:resource_or_aud` profile, `resource_client` must match one of the values in `required_aud`. A single-element `required_aud` automatically infers the client id, ensuring the same client identifier is shared with downstream BFF/Pundit integrations.
73
73
 
74
+ ## Keycloak Integration
75
+
76
+ ### Default Audience Behavior
77
+
78
+ Keycloak access tokens include `aud: "account"` by default. This is often unexpected for developers who expect the client ID to appear in the audience claim.
79
+
80
+ | Token Type | Default `aud` Value |
81
+ |------------|---------------------|
82
+ | Access Token | `"account"` |
83
+ | ID Token | Client ID |
84
+
85
+ ### Common Issue
86
+
87
+ ```ruby
88
+ # Configuration
89
+ config.verikloak.audience = 'rails-api'
90
+
91
+ # Actual token payload
92
+ {
93
+ "aud": "account", # NOT "rails-api"!
94
+ "sub": "user-123",
95
+ ...
96
+ }
97
+
98
+ # Result: 403 Forbidden - audience validation fails
99
+ ```
100
+
101
+ ### Solution: Configure Audience Mapper in Keycloak
102
+
103
+ To add a custom audience to access tokens:
104
+
105
+ 1. Go to **Clients** → Select your client (e.g., `rails-api`)
106
+ 2. Navigate to **Client scopes** tab
107
+ 3. Click on the dedicated scope (e.g., `rails-api-dedicated`)
108
+ 4. Go to **Mappers** tab → **Add mapper** → **By configuration**
109
+ 5. Select **Audience**
110
+ 6. Configure:
111
+ - **Name**: `rails-api-audience`
112
+ - **Included Client Audience**: `rails-api`
113
+ - **Add to access token**: ON
114
+ 7. Save
115
+
116
+ After this configuration, tokens will include:
117
+ ```json
118
+ {
119
+ "aud": ["rails-api", "account"],
120
+ ...
121
+ }
122
+ ```
123
+
124
+ ### Configuration Examples
125
+
126
+ #### Option 1: Allow both custom and account audience
127
+
128
+ ```ruby
129
+ # config/initializers/verikloak.rb
130
+ Rails.application.configure do
131
+ config.verikloak.audience = ['rails-api', 'account']
132
+ end
133
+ ```
134
+
135
+ #### Option 2: Use :allow_account profile
136
+
137
+ ```ruby
138
+ # With verikloak-audience middleware
139
+ use Verikloak::Audience::Middleware,
140
+ profile: :allow_account,
141
+ required_aud: ['rails-api']
142
+ ```
143
+
144
+ #### Option 3: Strict single audience (requires Keycloak Mapper)
145
+
146
+ ```ruby
147
+ # Only works if Keycloak Audience Mapper is configured
148
+ use Verikloak::Audience::Middleware,
149
+ profile: :strict_single,
150
+ required_aud: ['rails-api']
151
+ ```
152
+
153
+ ### Troubleshooting
154
+
155
+ #### "Audience validation failed" errors
156
+
157
+ 1. Check your token's `aud` claim:
158
+ ```bash
159
+ # Decode JWT (paste your token)
160
+ echo "YOUR_TOKEN" | cut -d. -f2 | base64 -d | jq .aud
161
+ ```
162
+
163
+ 2. If `aud` is only `"account"`:
164
+ - Add an Audience Mapper in Keycloak (see above)
165
+ - OR use `:allow_account` profile
166
+
167
+ 3. If using oauth2-proxy:
168
+ - Ensure the correct client ID is configured
169
+ - Check that the token is being forwarded correctly
170
+
74
171
  ## Testing
75
172
  All pull requests and pushes are automatically tested with [RSpec](https://rspec.info/) and [RuboCop](https://rubocop.org/) via GitHub Actions.
76
173
  See the CI badge at the top for current build status.
@@ -2,6 +2,10 @@
2
2
 
3
3
  # Insert the audience middleware after the core Verikloak middleware once it
4
4
  # has been loaded into the application.
5
+ #
6
+ # The Railtie's insert_middleware method includes internal guards to prevent
7
+ # duplicate insertion when both the railtie initializer and this generated
8
+ # initializer are loaded. This is safe to call multiple times.
5
9
  if defined?(Rails) && Rails.respond_to?(:application)
6
10
  Verikloak::Audience::Railtie.insert_middleware(Rails.application)
7
11
  end
@@ -28,7 +28,9 @@ module Verikloak
28
28
  # when available.
29
29
  #
30
30
  # @param app [Rails::Application] the Rails application instance
31
- initializer 'verikloak_audience.middleware' do |app|
31
+ initializer 'verikloak_audience.middleware',
32
+ after: 'verikloak.configure',
33
+ before: :build_middleware_stack do |app|
32
34
  # Insert automatically after core verikloak if present
33
35
  self.class.insert_middleware(app)
34
36
  end
@@ -42,6 +44,17 @@ module Verikloak
42
44
  end
43
45
  end
44
46
 
47
+ # Tracks whether middleware insertion has been attempted to prevent
48
+ # duplicate insertions when both railtie and generator initializer run.
49
+ # In Rails 8.x+, middleware operations may be queued and `include?` may
50
+ # not reflect pending insertions, so we use this flag as an additional
51
+ # safeguard.
52
+ @middleware_insertion_attempted = false
53
+
54
+ class << self
55
+ attr_accessor :middleware_insertion_attempted
56
+ end
57
+
45
58
  # Performs the insertion into the middleware stack when the core
46
59
  # Verikloak middleware is available and already present. Extracted for
47
60
  # testability without requiring a full Rails boot process.
@@ -49,22 +62,72 @@ module Verikloak
49
62
  # Insert the audience middleware after the base Verikloak middleware when
50
63
  # both are available on the stack.
51
64
  #
65
+ # In Rails 8.x+, middleware stack operations may be queued rather than
66
+ # immediately applied, so `include?` checks may return false even when
67
+ # the middleware will be inserted. We use `insert_after` with exception
68
+ # handling to gracefully handle this case.
69
+ #
52
70
  # @param app [#middleware] An object exposing a Rack middleware stack via `#middleware`.
53
71
  # @return [void]
54
72
  def self.insert_middleware(app)
55
73
  return unless defined?(::Verikloak::Middleware)
56
74
 
57
- middleware_stack = app.middleware
58
- return unless middleware_stack.respond_to?(:include?)
75
+ # Skip if we have already attempted insertion (handles Rails 8+ queued operations)
76
+ return if middleware_insertion_attempted
59
77
 
60
- return if middleware_stack.include?(::Verikloak::Audience::Middleware)
78
+ # Use app.config.middleware for queued operations in Rails 8.x+
79
+ # This ensures the insert_after operation is queued and applied during
80
+ # build_middleware_stack, maintaining proper ordering with verikloak-rails
81
+ middleware_stack = app.respond_to?(:config) ? app.config.middleware : app.middleware
61
82
 
62
- unless middleware_stack.include?(::Verikloak::Middleware)
63
- warn_missing_core_middleware
83
+ # Skip if already present (avoid duplicate insertion)
84
+ if middleware_stack.respond_to?(:include?) &&
85
+ middleware_stack.include?(::Verikloak::Audience::Middleware)
64
86
  return
65
87
  end
66
88
 
67
- middleware_stack.insert_after ::Verikloak::Middleware, ::Verikloak::Audience::Middleware
89
+ # Mark as attempted before insertion to prevent concurrent/subsequent calls
90
+ self.middleware_insertion_attempted = true
91
+
92
+ # Attempt to insert after the core Verikloak middleware.
93
+ # In Rails 8.x+, the middleware may be queued but not yet visible via include?,
94
+ # so we try the insertion and handle any exceptions gracefully.
95
+ begin
96
+ middleware_stack.insert_after ::Verikloak::Middleware, ::Verikloak::Audience::Middleware
97
+ rescue StandardError => e
98
+ # Handle middleware not found errors (varies by Rails version):
99
+ # - Rails 8+: RuntimeError with "No such middleware" message
100
+ # - Earlier: ActionDispatch::MiddlewareStack::MiddlewareNotFound
101
+ raise unless middleware_not_found_error?(e)
102
+
103
+ warn_missing_core_middleware
104
+ end
105
+ end
106
+
107
+ # Determines if the given exception indicates a middleware not found error.
108
+ # This handles variations across Rails versions:
109
+ # - Rails 8+: RuntimeError with "No such middleware" message
110
+ # - Rails 7 and earlier: ActionDispatch::MiddlewareStack::MiddlewareNotFound
111
+ #
112
+ # @param error [StandardError] the exception to check
113
+ # @return [Boolean] true if the error indicates missing middleware
114
+ def self.middleware_not_found_error?(error)
115
+ # Check exception class name (works for Rails 7's MiddlewareNotFound)
116
+ return true if error.class.name.to_s.include?('MiddlewareNotFound')
117
+
118
+ # Check message patterns for Rails 8+ RuntimeError
119
+ message = error.message.to_s
120
+ message.include?('No such middleware') ||
121
+ message.include?('does not exist') ||
122
+ message.match?(/middleware.*not found/i)
123
+ end
124
+
125
+ # Resets the insertion flag. Primarily used for testing to allow
126
+ # multiple insertion attempts within the same process.
127
+ #
128
+ # @return [void]
129
+ def self.reset_middleware_insertion_flag!
130
+ self.middleware_insertion_attempted = false
68
131
  end
69
132
 
70
133
  WARNING_MESSAGE = <<~MSG
@@ -4,6 +4,6 @@ module Verikloak
4
4
  module Audience
5
5
  # Current gem version.
6
6
  # @return [String]
7
- VERSION = '0.2.8'
7
+ VERSION = '0.3.0'
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: verikloak-audience
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.8
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - taiyaky
@@ -35,7 +35,7 @@ dependencies:
35
35
  requirements:
36
36
  - - ">="
37
37
  - !ruby/object:Gem::Version
38
- version: 0.2.0
38
+ version: 0.3.0
39
39
  - - "<"
40
40
  - !ruby/object:Gem::Version
41
41
  version: 1.0.0
@@ -45,7 +45,7 @@ dependencies:
45
45
  requirements:
46
46
  - - ">="
47
47
  - !ruby/object:Gem::Version
48
- version: 0.2.0
48
+ version: 0.3.0
49
49
  - - "<"
50
50
  - !ruby/object:Gem::Version
51
51
  version: 1.0.0
@@ -76,7 +76,7 @@ metadata:
76
76
  source_code_uri: https://github.com/taiyaky/verikloak-audience
77
77
  changelog_uri: https://github.com/taiyaky/verikloak-audience/blob/main/CHANGELOG.md
78
78
  bug_tracker_uri: https://github.com/taiyaky/verikloak-audience/issues
79
- documentation_uri: https://rubydoc.info/gems/verikloak-audience/0.2.8
79
+ documentation_uri: https://rubydoc.info/gems/verikloak-audience/0.3.0
80
80
  rubygems_mfa_required: 'true'
81
81
  rdoc_options: []
82
82
  require_paths: