verikloak 0.1.4 → 0.1.5

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: 3b7e22a96384ccea0cb43c2c611f7106d0e88b23793279f2ee105d68da226bb0
4
- data.tar.gz: d83ba8ebe7834895d1a3f6e39c26dae02713611fc767177c909987ef1111cf50
3
+ metadata.gz: 41b02d67b2d6182f8af59436549e9d68cccf08559ecdee0539793ee3baef77a7
4
+ data.tar.gz: 5cd807c55d6635370149cf0090644698f470d15c9e01dc78a9332adb6659e2f8
5
5
  SHA512:
6
- metadata.gz: 40481fc255490d0c22c2808cf00e8c0e136488f755e6dc2e388c4ab81978975c6ee478a2916d61fe564bd25984898f74ddeccacfe1ae8006a081b3bfc441bc41
7
- data.tar.gz: 0aaf942838e33a618197d0fb6d5446c6a9ffaa5b529db50902f2ab403b884b03ee0c87bdf8c99e4c8b069cad1f566026bb05fb16ab3583339493a4891eeb4f88
6
+ metadata.gz: eba3a601c8c67080326955bd0557cfcc8d8098469694ef42ffac590b10a91c48cf4c3cb7250645d6db39e8208a8a05bd0bfa8b59cbdb9ced6e57e55241aa4da1
7
+ data.tar.gz: 553050d22b04de79bac27c00358f2c1a0779d380556297ffa2ca7bb5fa1944fe9ee55f9450ccc52cf004c7899c880484457b703d149404e80097079fff303341
data/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.1.5] - 2025-09-21
11
+
12
+ ### Added
13
+ - Specs for `Verikloak::HTTP.default_connection`, ensuring retry middleware and timeout defaults stay in sync.
14
+
15
+ ### Changed
16
+ - Middleware audience callables now handle zero-arity and BasicObject-style implementations without relying on `method(:call)`.
17
+ - README documents the shared `Verikloak::HTTP.default_connection` helper for reuse/customization.
18
+
19
+ ### Dependencies
20
+ - Declare `faraday-retry` as a runtime dependency so the default HTTP connection can load the retry middleware.
21
+
22
+ ---
23
+
10
24
  ## [0.1.4] - 2025-09-20
11
25
 
12
26
  ### Chore
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Verikloak
1
+ # verikloak
2
2
 
3
3
  [![CI](https://github.com/taiyaky/verikloak/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/taiyaky/verikloak/actions/workflows/ci.yml)
4
4
  [![Gem Version](https://img.shields.io/gem/v/verikloak)](https://rubygems.org/gems/verikloak)
@@ -9,8 +9,6 @@ A lightweight Rack middleware for verifying Keycloak JWT access tokens via OpenI
9
9
 
10
10
  Verikloak is a plug-and-play solution for Ruby (especially Rails API) apps that need to validate incoming `Bearer` tokens issued by Keycloak. It uses OpenID Connect Discovery and JWKs to fetch the public keys and verify JWT signatures securely.
11
11
 
12
- ---
13
-
14
12
  ## Features
15
13
 
16
14
  - OpenID Connect Discovery (`.well-known/openid-configuration`)
@@ -20,8 +18,6 @@ Verikloak is a plug-and-play solution for Ruby (especially Rails API) apps that
20
18
  - Rails/Rack middleware support
21
19
  - Faraday-based customizable HTTP layer
22
20
 
23
- ---
24
-
25
21
  ## Installation
26
22
 
27
23
  Add this line to your application's `Gemfile`:
@@ -36,8 +32,6 @@ Then install:
36
32
  bundle install
37
33
  ```
38
34
 
39
- ---
40
-
41
35
  ## Usage
42
36
 
43
37
  ### Rails (API mode)
@@ -81,7 +75,6 @@ structured error responses.
81
75
  > **Note:** When the Rack middleware is enabled, it already renders JSON error responses.
82
76
  > The `rescue_from` example above is only necessary if you bypass the middleware or want custom behavior.
83
77
 
84
- ---
85
78
  #### Error Hierarchy
86
79
 
87
80
  All Verikloak errors inherit from `Verikloak::Error`:
@@ -90,7 +83,7 @@ All Verikloak errors inherit from `Verikloak::Error`:
90
83
  - `Verikloak::DiscoveryError` – OIDC discovery fetch/parse (`503 Service Unavailable`)
91
84
  - `Verikloak::JwksCacheError` – JWKs fetch/parse/cache (`503 Service Unavailable`)
92
85
  - `Verikloak::MiddlewareError` – header/infra issues surfaced by the middleware (usually `401`, sometimes `503`)
93
- ---
86
+
94
87
  #### Recommended: use environment variables in production
95
88
 
96
89
  ```ruby
@@ -100,7 +93,7 @@ config.middleware.use Verikloak::Middleware,
100
93
  skip_paths: ['/', '/health', '/public/*', '/rails/*']
101
94
  ```
102
95
  This makes the configuration secure and flexible across environments.
103
- ---
96
+
104
97
  ### Accessing claims in controllers
105
98
 
106
99
  Once the middleware is enabled, Verikloak adds the decoded token and raw JWT to the Rack environment.
@@ -117,7 +110,7 @@ class Api::V1::NotesController < ApplicationController
117
110
  end
118
111
  end
119
112
  ```
120
- ---
113
+
121
114
  ### Standalone Rack app
122
115
 
123
116
  ```ruby
@@ -133,7 +126,6 @@ run ->(env) {
133
126
  [200, { "Content-Type" => "application/json" }, [user.to_json]]
134
127
  }
135
128
  ```
136
- ---
137
129
 
138
130
  ## How It Works
139
131
 
@@ -149,8 +141,6 @@ run ->(env) {
149
141
  - `nbf` (not before)
150
142
  7. Makes the decoded payload available in `env["verikloak.user"]`
151
143
 
152
- ---
153
-
154
144
  ## Error Responses
155
145
 
156
146
  Verikloak returns JSON error responses in a consistent format with structured error codes. The HTTP status code reflects the nature of the error: 401 for client-side authentication issues, 503 for server-side discovery/JWKs errors, and 500 for unexpected internal errors.
@@ -236,13 +226,13 @@ For a full list of error cases and detailed explanations, please see the [ERRORS
236
226
  | Key | Required | Description |
237
227
  | --------------- | -------- | ------------------------------------------- |
238
228
  | `discovery_url` | Yes | Full URL to your realm's OIDC discovery doc |
239
- | `audience` | Yes | Your client ID (checked against `aud`) |
229
+ | `audience` | Yes | Your client ID (checked against `aud`). Accepts a String or callable returning a String/Array per request. |
240
230
  | `skip_paths` | No | Array of paths or wildcards to skip authentication, e.g. `['/', '/health', '/public/*']`. **Note:** Regex patterns are not supported. |
241
231
  | `discovery` | No | Inject custom Discovery instance (advanced/testing) |
242
232
  | `jwks_cache` | No | Inject custom JwksCache instance (advanced/testing) |
243
233
  | `leeway` | No | Clock skew tolerance (seconds) applied during JWT verification. Defaults to `TokenDecoder::DEFAULT_LEEWAY`. |
244
234
  | `token_verify_options` | No | Hash of advanced JWT verification options passed through to TokenDecoder. For example: `{ verify_iat: false, leeway: 10, algorithms: ["RS256"] }`. If both `leeway:` and `token_verify_options[:leeway]` are set, the latter takes precedence. |
245
- | `connection` | No | Inject a Faraday::Connection used for both Discovery and JWKs fetches. Allows unified timeout, retry, and headers. |
235
+ | `connection` | No | Inject a Faraday::Connection used for both Discovery and JWKs fetches. Defaults to a safe connection with timeouts and retries. |
246
236
 
247
237
  #### Option: `skip_paths`
248
238
 
@@ -264,33 +254,51 @@ Paths **not matched** by any `skip_paths` entry will require a valid JWT.
264
254
  **Note:** Regex patterns are not supported. Only literal paths and `*` wildcards are allowed.
265
255
  Internally, `*` expands to match nested paths, so patterns like `/rails/*` are valid. This differs from regex — for example, `'/rails'` alone matches only `/rails`, while `'/rails/*'` covers both `/rails` and deeper subpaths.
266
256
 
257
+ #### Option: `audience`
258
+
259
+ The `audience` option may be either a static String or any callable object (Proc, lambda, object responding to `#call`). When a callable is provided it receives the Rack `env` and can return a different audience for each request. This is useful when a single gateway serves multiple downstream clients:
260
+
261
+ ```ruby
262
+ Verikloak::Middleware.new(app,
263
+ discovery_url: ENV['DISCOVERY_URL'],
264
+ audience: ->(env) {
265
+ env['PATH_INFO'].start_with?('/admin') ? 'admin-client-id' : 'public-client-id'
266
+ }
267
+ )
268
+ ```
269
+
270
+ The callable may also return an Array of audiences when a route is valid for multiple clients.
271
+
267
272
  #### Customizing Faraday for Discovery and JWKs
268
273
 
269
- Both `Discovery` and `JwksCache` accept a `Faraday::Connection`.
270
- This allows you to configure timeouts, retries, logging, and shared headers:
274
+ Both `Discovery` and `JwksCache` accept a `Faraday::Connection`.
275
+ Verikloak ships with a helper you can re-use anywhere:
276
+
277
+ ```ruby
278
+ connection = Verikloak::HTTP.default_connection
279
+ ```
280
+
281
+ The default connection enables retries (via `faraday-retry`) for idempotent GET requests and applies conservative timeouts (5s request / 2s open). If you need to add extra middleware, adapters, or instrumentation, you can build on top of the defaults:
271
282
 
272
283
  ```ruby
273
- connection = Faraday.new(request: { timeout: 5 }) do |f|
284
+ connection = Faraday.new(request: { timeout: 10 }) do |f|
285
+ f.request :retry, Verikloak::HTTP::RETRY_OPTIONS
274
286
  f.response :logger
287
+ f.adapter Faraday.default_adapter
275
288
  end
276
289
 
277
290
  config.middleware.use Verikloak::Middleware,
278
291
  discovery_url: ENV["DISCOVERY_URL"],
279
292
  audience: ENV["CLIENT_ID"],
280
- jwks_cache: Verikloak::JwksCache.new(
281
- jwks_uri: "https://example.com/realms/myrealm/protocol/openid-connect/certs",
282
- connection: connection
283
- )
284
- ```
285
- This makes it easy to apply consistent Faraday settings across both discovery and JWKs fetches.
293
+ connection: connection
286
294
 
287
- ```ruby
288
- # Alternatively, you can pass the connection directly to the middleware:
289
- config.middleware.use Verikloak::Middleware,
290
- discovery_url: ENV["DISCOVERY_URL"],
291
- audience: ENV["CLIENT_ID"],
295
+ # Or pass the connection through to a shared JwksCache instance
296
+ jwks_cache = Verikloak::JwksCache.new(
297
+ jwks_uri: "https://example.com/realms/myrealm/protocol/openid-connect/certs",
292
298
  connection: connection
299
+ )
293
300
  ```
301
+ This makes it easy to keep HTTP settings consistent across discovery, JWK refreshes, and any other Verikloak components you wire together.
294
302
 
295
303
  #### Customizing token verification (leeway and options)
296
304
 
@@ -314,8 +322,6 @@ config.middleware.use Verikloak::Middleware,
314
322
  - `token_verify_options:` is passed directly to TokenDecoder (and ultimately to `JWT.decode`).
315
323
  - If both are set, `token_verify_options[:leeway]` takes precedence.
316
324
 
317
- ---
318
-
319
325
  #### Performance note
320
326
 
321
327
  Internally, Verikloak caches `TokenDecoder` instances per JWKs fetch to avoid reinitializing
@@ -330,14 +336,13 @@ Verikloak consists of modular components, each with a focused responsibility:
330
336
  |----------------|--------------------------------------------------------|--------------|
331
337
  | `Middleware` | Rack-compatible entry point for token validation | Rack layer |
332
338
  | `Discovery` | Fetches OIDC discovery metadata (`.well-known`) | Network layer|
339
+ | `HTTP` | Provides shared Faraday connection with retries/timeouts | Network layer|
333
340
  | `JwksCache` | Fetches & caches JWKs public keys (with ETag) | Cache layer |
334
341
  | `TokenDecoder` | Decodes and verifies JWTs (signature, exp, nbf, iss, aud) | Crypto layer |
335
342
  | `Errors` | Centralized error hierarchy | Core layer |
336
343
 
337
344
  This separation enables better testing, modular reuse, and flexibility.
338
345
 
339
- ---
340
-
341
346
  ## Development (for contributors)
342
347
 
343
348
  Clone and install dependencies:
@@ -349,8 +354,6 @@ bundle install
349
354
  ```
350
355
  See **Testing** below to run specs and RuboCop. For releasing, see **Publishing**.
351
356
 
352
- ---
353
-
354
357
  ## Testing
355
358
 
356
359
  All pull requests and pushes are automatically tested with [RSpec](https://rspec.info/) and [RuboCop](https://rubocop.org/) via GitHub Actions.
@@ -362,40 +365,33 @@ To run the test suite locally:
362
365
  docker compose run --rm dev rspec
363
366
  docker compose run --rm dev rubocop
364
367
  ```
365
- ---
366
368
 
367
369
  ## Contributing
368
370
 
369
371
  Bug reports and pull requests are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
370
372
 
371
- ---
372
-
373
373
  ## Security
374
374
 
375
375
  If you find a security vulnerability, please follow the instructions in [SECURITY.md](SECURITY.md).
376
376
 
377
- ---
378
-
379
377
  ## License
380
378
 
381
379
  This project is licensed under the [MIT License](LICENSE).
382
380
 
383
- ---
384
-
385
381
  ## Publishing (for maintainers)
386
382
 
387
383
  Gem release instructions are documented separately in [MAINTAINERS.md](MAINTAINERS.md).
388
384
 
389
- ---
390
-
391
385
  ## Changelog
392
386
 
393
387
  See [CHANGELOG.md](CHANGELOG.md) for release history.
394
388
 
395
- ---
396
-
397
389
  ## References
398
390
 
399
- - [OpenID Connect Discovery 1.0 Spec](https://openid.net/specs/openid-connect-discovery-1_0.html)
400
- - [Keycloak Documentation: Securing Apps](https://www.keycloak.org/docs/latest/securing_apps/#openid-connect)
401
- - [JWT RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519)
391
+ - [OpenID Connect Discovery 1.0 Spec](https://openid.net/specs/openid-connect-discovery-1_0.html)
392
+ - [Keycloak Documentation: Securing Apps](https://www.keycloak.org/docs/latest/securing_apps/#openid-connect)
393
+ - [JWT RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519)
394
+ - [verikloak-rails on RubyGems](https://rubygems.org/gems/verikloak-rails)
395
+ - [verikloak-bff on RubyGems](https://rubygems.org/gems/verikloak-bff)
396
+ - [verikloak-pundit on RubyGems](https://rubygems.org/gems/verikloak-pundit)
397
+ - [verikloak-audience on RubyGems](https://rubygems.org/gems/verikloak-audience)
@@ -4,6 +4,8 @@ require 'faraday'
4
4
  require 'json'
5
5
  require 'uri'
6
6
 
7
+ require 'verikloak/http'
8
+
7
9
  module Verikloak
8
10
  # Fetches and caches the OpenID Connect Discovery document.
9
11
  #
@@ -39,10 +41,10 @@ module Verikloak
39
41
  REQUIRED_FIELDS = %w[jwks_uri issuer].freeze
40
42
 
41
43
  # @param discovery_url [String] The full URL to the `.well-known/openid-configuration`.
42
- # @param connection [Faraday::Connection] Optional Faraday client (for DI/tests). Defaults to `Faraday.new`.
44
+ # @param connection [Faraday::Connection] Optional Faraday client (for DI/tests).
43
45
  # @param cache_ttl [Integer] Cache TTL in seconds (default: 3600).
44
46
  # @raise [DiscoveryError] when `discovery_url` is not a valid HTTP(S) URL
45
- def initialize(discovery_url:, connection: Faraday.new, cache_ttl: 3600)
47
+ def initialize(discovery_url:, connection: Verikloak::HTTP.default_connection, cache_ttl: 3600)
46
48
  unless discovery_url.is_a?(String) && discovery_url.strip.match?(%r{^https?://})
47
49
  raise DiscoveryError.new('Invalid discovery URL: must be a non-empty HTTP(S) URL',
48
50
  code: 'invalid_discovery_url')
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'faraday/retry'
5
+
6
+ module Verikloak
7
+ # Internal HTTP helpers shared across components.
8
+ module HTTP
9
+ # Default request timeout (seconds) for outbound discovery/JWKs calls.
10
+ DEFAULT_TIMEOUT = 5
11
+ # Default open/read timeout (seconds) before establishing the HTTP connection.
12
+ DEFAULT_OPEN_TIMEOUT = 2
13
+
14
+ # Retry middleware configuration used for idempotent GET requests.
15
+ # Retries on 429/5xx with exponential backoff and jitter.
16
+ RETRY_OPTIONS = {
17
+ max: 2,
18
+ interval: 0.1,
19
+ interval_randomness: 0.2,
20
+ backoff_factor: 2,
21
+ methods: %i[get],
22
+ retry_statuses: [429, 500, 502, 503, 504]
23
+ }.freeze
24
+
25
+ # Builds a Faraday connection with conservative defaults suitable for
26
+ # network-bound operations (discovery and JWKs fetching).
27
+ #
28
+ # @return [Faraday::Connection]
29
+ def self.default_connection
30
+ Faraday.new do |f|
31
+ f.request :retry, RETRY_OPTIONS
32
+ f.options.timeout = DEFAULT_TIMEOUT
33
+ f.options.open_timeout = DEFAULT_OPEN_TIMEOUT
34
+ f.adapter Faraday.default_adapter
35
+ end
36
+ end
37
+ end
38
+ end
@@ -3,6 +3,8 @@
3
3
  require 'faraday'
4
4
  require 'json'
5
5
 
6
+ require 'verikloak/http'
7
+
6
8
  module Verikloak
7
9
  # Caches and revalidates JSON Web Key Sets (JWKs) fetched from a remote endpoint.
8
10
  #
@@ -43,7 +45,7 @@ module Verikloak
43
45
  end
44
46
 
45
47
  @jwks_uri = jwks_uri
46
- @connection = connection || Faraday.new
48
+ @connection = connection || Verikloak::HTTP.default_connection
47
49
  @cached_keys = nil
48
50
  @etag = nil
49
51
  @fetched_at = nil
@@ -59,6 +61,8 @@ module Verikloak
59
61
  # @return [Array&lt;Hash&gt;] the cached JWKs after fetch/revalidation
60
62
  # @raise [JwksCacheError] on HTTP failures, invalid JSON, invalid structure, or cache miss on 304
61
63
  def fetch!
64
+ return @cached_keys if fresh_by_ttl?
65
+
62
66
  with_error_handling do
63
67
  # Build conditional request headers (ETag-based)
64
68
  headers = build_conditional_headers
@@ -5,6 +5,8 @@ require 'json'
5
5
  require 'set'
6
6
  require 'faraday'
7
7
 
8
+ require 'verikloak/http'
9
+
8
10
  module Verikloak
9
11
  # @api private
10
12
  #
@@ -106,12 +108,12 @@ module Verikloak
106
108
 
107
109
  # Returns a cached TokenDecoder instance for current inputs.
108
110
  # Cache key uses issuer, audience, leeway, token_verify_options, and JWKs fetched_at timestamp.
109
- def decoder_for
111
+ def decoder_for(audience)
110
112
  keys = @jwks_cache.cached
111
113
  fetched_at = @jwks_cache.respond_to?(:fetched_at) ? @jwks_cache.fetched_at : nil
112
114
  cache_key = [
113
115
  @issuer,
114
- @audience,
116
+ audience,
115
117
  @leeway,
116
118
  @token_verify_options,
117
119
  fetched_at
@@ -120,7 +122,7 @@ module Verikloak
120
122
  @decoder_cache[cache_key] ||= TokenDecoder.new(
121
123
  jwks: keys,
122
124
  issuer: @issuer,
123
- audience: @audience,
125
+ audience: audience,
124
126
  leeway: @leeway,
125
127
  options: @token_verify_options
126
128
  )
@@ -142,14 +144,16 @@ module Verikloak
142
144
  # @param token [String]
143
145
  # @return [Hash] decoded JWT claims
144
146
  # @raise [Verikloak::Error] bubbles up verification/fetch errors for centralized handling
145
- def decode_token(token)
147
+ def decode_token(env, token)
146
148
  ensure_jwks_cache!
147
149
  if @jwks_cache.cached.nil? || @jwks_cache.cached.empty?
148
150
  raise MiddlewareError.new('JWKs cache is empty, cannot verify token', code: 'jwks_cache_miss')
149
151
  end
150
152
 
153
+ audience = resolve_audience(env)
154
+
151
155
  # First attempt
152
- decoder = decoder_for
156
+ decoder = decoder_for(audience)
153
157
 
154
158
  begin
155
159
  decoder.decode!(token)
@@ -160,11 +164,78 @@ module Verikloak
160
164
  refresh_jwks!
161
165
 
162
166
  # Rebuild decoder with refreshed keys and try once more.
163
- decoder = decoder_for
167
+ decoder = decoder_for(audience)
164
168
  decoder.decode!(token)
165
169
  end
166
170
  end
167
171
 
172
+ # Resolves the expected audience for the current request.
173
+ #
174
+ # @param env [Hash] Rack environment.
175
+ # @return [String, Array<String>] The expected audience value.
176
+ # @raise [MiddlewareError] when the resolved audience is blank.
177
+ def resolve_audience(env)
178
+ source = @audience_source
179
+ value = if source.respond_to?(:call)
180
+ callable = source
181
+ arity = callable.respond_to?(:arity) ? callable.arity : safe_callable_arity(callable)
182
+ call_with_optional_env(callable, env, arity)
183
+ else
184
+ source
185
+ end
186
+
187
+ raise MiddlewareError.new('Audience is blank for the request', code: 'invalid_audience') if value.nil?
188
+
189
+ if value.is_a?(Array)
190
+ raise MiddlewareError.new('Audience is blank for the request', code: 'invalid_audience') if value.empty?
191
+
192
+ return value
193
+ end
194
+
195
+ normalized = value.to_s
196
+ raise MiddlewareError.new('Audience is blank for the request', code: 'invalid_audience') if normalized.empty?
197
+
198
+ normalized
199
+ end
200
+
201
+ # Invokes the audience callable, passing the Rack env only when required.
202
+ # Falls back to a zero-argument invocation if the callable raises
203
+ # `ArgumentError` due to an unexpected argument.
204
+ #
205
+ # @param callable [#call] Audience resolver callable.
206
+ # @param env [Hash] Rack environment.
207
+ # @param arity [Integer, nil] Callable arity when known, nil otherwise.
208
+ # @return [Object] Audience value returned by the callable.
209
+ # @raise [ArgumentError] when the callable raises for reasons other than arity mismatch.
210
+ def call_with_optional_env(callable, env, arity)
211
+ return callable.call if arity&.zero?
212
+
213
+ callable.call(env)
214
+ rescue ArgumentError => e
215
+ raise unless arity.nil? && wrong_arity_error?(e)
216
+
217
+ callable.call
218
+ end
219
+
220
+ # Safely obtains a callable's arity, returning nil when `#method(:call)`
221
+ # cannot be resolved (e.g., BasicObject-based objects).
222
+ #
223
+ # @param callable [#call]
224
+ # @return [Integer, nil]
225
+ def safe_callable_arity(callable)
226
+ callable.method(:call).arity
227
+ rescue NameError
228
+ nil
229
+ end
230
+
231
+ # Returns true when the ArgumentError message indicates a wrong arity.
232
+ #
233
+ # @param error [ArgumentError]
234
+ # @return [Boolean]
235
+ def wrong_arity_error?(error)
236
+ error.message.include?('wrong number of arguments')
237
+ end
238
+
168
239
  # Ensures that discovery metadata and JWKs cache are initialized and up-to-date.
169
240
  # This method is thread-safe.
170
241
  #
@@ -280,11 +351,13 @@ module Verikloak
280
351
 
281
352
  # @param app [#call] downstream Rack app
282
353
  # @param discovery_url [String] OIDC discovery endpoint URL
283
- # @param audience [String] expected `aud` claim
354
+ # @param audience [String, #call] Expected `aud` claim. When a callable is provided it
355
+ # receives the Rack env and may return a String or Array of audiences.
284
356
  # @param skip_paths [Array<String>] literal paths or wildcard patterns to bypass auth
285
357
  # @param discovery [Discovery, nil] custom discovery instance (for DI/tests)
286
358
  # @param jwks_cache [JwksCache, nil] custom JWKs cache instance (for DI/tests)
287
- # @param connection [Faraday::Connection, nil] Optional injected Faraday connection (defaults to Faraday.new)
359
+ # @param connection [Faraday::Connection, nil] Optional injected Faraday connection
360
+ # (defaults to {Verikloak::HTTP.default_connection})
288
361
  # @param leeway [Integer] Clock skew tolerance in seconds for token verification (delegated to TokenDecoder)
289
362
  # @param token_verify_options [Hash] Additional JWT verification options passed through
290
363
  # to TokenDecoder.
@@ -298,12 +371,12 @@ module Verikloak
298
371
  connection: nil,
299
372
  leeway: Verikloak::TokenDecoder::DEFAULT_LEEWAY,
300
373
  token_verify_options: {})
301
- @app = app
302
- @audience = audience
303
- @discovery = discovery || Discovery.new(discovery_url: discovery_url)
304
- @jwks_cache = jwks_cache
305
- @connection = connection || Faraday.new
306
- @leeway = leeway
374
+ @app = app
375
+ @connection = connection || Verikloak::HTTP.default_connection
376
+ @audience_source = audience
377
+ @discovery = discovery || Discovery.new(discovery_url: discovery_url, connection: @connection)
378
+ @jwks_cache = jwks_cache
379
+ @leeway = leeway
307
380
  @token_verify_options = token_verify_options || {}
308
381
  @issuer = nil
309
382
  @mutex = Mutex.new
@@ -344,7 +417,7 @@ module Verikloak
344
417
  # @param token [String]
345
418
  # @return [Array(Integer, Hash, Array<String>)] Rack response triple
346
419
  def handle_request(env, token)
347
- claims = decode_token(token)
420
+ claims = decode_token(env, token)
348
421
  env['verikloak.token'] = token
349
422
  env['verikloak.user'] = claims
350
423
  @app.call(env)
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Verikloak
4
4
  # Defines the current version of the Verikloak gem.
5
- VERSION = '0.1.4'
5
+ VERSION = '0.1.5'
6
6
  end
data/lib/verikloak.rb CHANGED
@@ -5,6 +5,7 @@
5
5
  # by simply requiring 'verikloak'.
6
6
  require 'verikloak/version'
7
7
  require 'verikloak/errors'
8
+ require 'verikloak/http'
8
9
  require 'verikloak/discovery'
9
10
  require 'verikloak/jwks_cache'
10
11
  require 'verikloak/token_decoder'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: verikloak
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - taiyaky
@@ -29,6 +29,26 @@ dependencies:
29
29
  - - "<"
30
30
  - !ruby/object:Gem::Version
31
31
  version: '3.0'
32
+ - !ruby/object:Gem::Dependency
33
+ name: faraday-retry
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '2.0'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '3.0'
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '2.0'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '3.0'
32
52
  - !ruby/object:Gem::Dependency
33
53
  name: json
34
54
  requirement: !ruby/object:Gem::Requirement
@@ -77,6 +97,7 @@ files:
77
97
  - lib/verikloak.rb
78
98
  - lib/verikloak/discovery.rb
79
99
  - lib/verikloak/errors.rb
100
+ - lib/verikloak/http.rb
80
101
  - lib/verikloak/jwks_cache.rb
81
102
  - lib/verikloak/middleware.rb
82
103
  - lib/verikloak/token_decoder.rb
@@ -88,7 +109,7 @@ metadata:
88
109
  source_code_uri: https://github.com/taiyaky/verikloak
89
110
  changelog_uri: https://github.com/taiyaky/verikloak/blob/main/CHANGELOG.md
90
111
  bug_tracker_uri: https://github.com/taiyaky/verikloak/issues
91
- documentation_uri: https://rubydoc.info/gems/verikloak/0.1.4
112
+ documentation_uri: https://rubydoc.info/gems/verikloak/0.1.5
92
113
  rubygems_mfa_required: 'true'
93
114
  rdoc_options: []
94
115
  require_paths: