verikloak 0.2.1 → 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: 743b7db62f39f179fe22804c61144e661992625e43f3b411ea965f85364b6f50
4
- data.tar.gz: ea9fea3cc08d53d076c60e946a39dad3594468dc16574b6a1f3a0e4574f9d80c
3
+ metadata.gz: 24bc2b5eb7f699c9f8bc4970a2b0b6cdca87b7c7e37d9718c51d85d8cf58a8cc
4
+ data.tar.gz: 439ec2fb34a67140476aff243addfaac8cb8d5ac18ef7b6915d3a9ba6dc5947d
5
5
  SHA512:
6
- metadata.gz: 89d6398d946686f61ec67dd93a2f807f38b4cc828bf86b4023787ea3aece53ebac95b08306827a06507883d1ef3f1711571af01db0c7ac5e2e4cc5340d92d34b
7
- data.tar.gz: 5469a3833cde3b5f5e21439cdd19bb6638ef0e3b49fd2da64e7398dcd24a8851eec11f985ab0821a23b7446d275983b62e87804b541299ec486b88113d8b89ee
6
+ metadata.gz: 61be82820e149b89c7e5dead4bc79aef1da0959b85ba447e7bf9d6a346efb331f378030620c2ee42d2b41e5c3e21d89346f405ad16b6264f9452fc49771fc2d4
7
+ data.tar.gz: 9bec7c747971154c59321505fdd2817487e09013eb8e32d3defe86a7dc38a4731c5e01d113639cd3eae1a33e9073b932dfa5d9a243b65a1609d9b3549cb48129
data/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.3.0] - 2025-12-31
11
+
12
+ ### Added
13
+ - **NEW**: `issuer` parameter for `Middleware#initialize` to optionally override the discovered issuer
14
+ - When provided, the configured issuer takes precedence over the OIDC discovery document's issuer
15
+ - This enables compatibility with `verikloak-rails` which passes `issuer` from configuration
16
+ - If not provided, the middleware continues to use the issuer from OIDC discovery (existing behavior)
17
+
18
+ ### Changed
19
+ - Internal issuer handling now distinguishes between `@configured_issuer` (user-provided) and `@issuer` (discovered/effective)
20
+ - When `jwks_cache` is injected, discovery is only fetched once (and skipped entirely if `issuer` is provided)
21
+
10
22
  ## [0.2.1] - 2025-09-23
11
23
 
12
24
  ### Changed
data/README.md CHANGED
@@ -41,10 +41,42 @@ Add the middleware in `config/application.rb`:
41
41
  ```ruby
42
42
  config.middleware.use Verikloak::Middleware,
43
43
  discovery_url: "https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration",
44
- audience: "your-client-id",
45
- skip_paths: ['/skip_path']
44
+ audience: "your-client-id"
46
45
  ```
47
46
 
47
+ For production environments, use environment variables:
48
+
49
+ ```ruby
50
+ config.middleware.use Verikloak::Middleware,
51
+ discovery_url: ENV.fetch("DISCOVERY_URL"),
52
+ audience: ENV.fetch("CLIENT_ID"),
53
+ skip_paths: ['/', '/health', '/public/*', '/rails/*']
54
+ ```
55
+
56
+ #### Advanced configuration
57
+
58
+ For more complex setups, Verikloak supports additional configuration options:
59
+
60
+ ```ruby
61
+ config.middleware.use Verikloak::Middleware,
62
+ discovery_url: ENV.fetch("DISCOVERY_URL"),
63
+ audience: ENV.fetch("CLIENT_ID"),
64
+ issuer: "https://custom.issuer.example.com/realms/myrealm", # Optional: override discovered issuer
65
+ skip_paths: ['/', '/health', '/public/*', '/rails/*'],
66
+ leeway: 30, # Allow 30 seconds clock skew
67
+ token_env_key: "rack.session.token", # Custom token storage key
68
+ user_env_key: "rack.session.claims", # Custom user claims storage key
69
+ realm: "my-api", # Custom realm for WWW-Authenticate header
70
+ logger: Rails.logger # Logger for internal errors
71
+ ```
72
+
73
+ **Additional middleware options:**
74
+
75
+ - `token_env_key` (default: `"verikloak.token"`) — where the raw JWT is stored in the Rack env
76
+ - `user_env_key` (default: `"verikloak.user"`) — where decoded claims are stored
77
+ - `realm` (default: `"verikloak"`) — value used in the `WWW-Authenticate` header for 401 responses
78
+ - `logger` — an object responding to `error` (and optionally `debug`) that receives unexpected 500-level failures
79
+
48
80
  #### Handling Authentication Failures
49
81
 
50
82
  When you use the Rack middleware, authentication failures are automatically converted into JSON error responses (for example, `401` for token issues, `503` for JWKs/discovery errors). In most cases **you do not need to add custom `rescue_from` handlers** in Rails controllers.
@@ -84,40 +116,25 @@ All Verikloak errors inherit from `Verikloak::Error`:
84
116
  - `Verikloak::JwksCacheError` – JWKs fetch/parse/cache (`503 Service Unavailable`)
85
117
  - `Verikloak::MiddlewareError` – header/infra issues surfaced by the middleware (usually `401`, sometimes `503`)
86
118
 
87
- #### Recommended: use environment variables in production
119
+ ### Standalone Rack app
88
120
 
89
121
  ```ruby
90
- config.middleware.use Verikloak::Middleware,
91
- discovery_url: ENV.fetch("DISCOVERY_URL"),
92
- audience: ENV.fetch("CLIENT_ID"),
93
- skip_paths: ['/', '/health', '/public/*', '/rails/*']
94
- ```
95
- This makes the configuration secure and flexible across environments.
96
-
97
- #### Advanced middleware options
98
-
99
- `Verikloak::Middleware` exposes a few optional knobs that help integrate with
100
- different Rack stacks:
122
+ # config.ru example for a standalone Rack app
123
+ require "verikloak"
101
124
 
102
- - `token_env_key` (default: `"verikloak.token"`) — where the raw JWT is stored in the Rack env
103
- - `user_env_key` (default: `"verikloak.user"`) — where decoded claims are stored
104
- - `realm` (default: `"verikloak"`) — value used in the `WWW-Authenticate` header for 401 responses
105
- - `logger` — an object responding to `error` (and optionally `debug`) that receives unexpected 500-level failures
125
+ use Verikloak::Middleware,
126
+ discovery_url: "https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration",
127
+ audience: "my-client-id"
106
128
 
107
- ```ruby
108
- config.middleware.use Verikloak::Middleware,
109
- discovery_url: ENV.fetch("DISCOVERY_URL"),
110
- audience: ENV.fetch("CLIENT_ID"),
111
- token_env_key: "rack.session.token",
112
- user_env_key: "rack.session.claims",
113
- realm: "my-api",
114
- logger: Rails.logger
129
+ run ->(env) {
130
+ user = env["verikloak.user"] # Decoded JWT claims hash (if token is valid)
131
+ [200, { "Content-Type" => "application/json" }, [user.to_json]]
132
+ }
115
133
  ```
116
134
 
117
135
  ### Accessing claims in controllers
118
136
 
119
- Once the middleware is enabled, Verikloak adds the decoded token and raw JWT to the Rack environment.
120
- You can access them in any Rails controller:
137
+ Once the middleware is enabled, Verikloak adds the decoded token and raw JWT to the Rack environment:
121
138
 
122
139
  ```ruby
123
140
  class Api::V1::NotesController < ApplicationController
@@ -131,22 +148,6 @@ class Api::V1::NotesController < ApplicationController
131
148
  end
132
149
  ```
133
150
 
134
- ### Standalone Rack app
135
-
136
- ```ruby
137
- # config.ru example for a standalone Rack app
138
- require "verikloak"
139
-
140
- use Verikloak::Middleware,
141
- discovery_url: "https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration",
142
- audience: "my-client-id"
143
-
144
- run ->(env) {
145
- user = env["verikloak.user"] # Decoded JWT claims hash (if token is valid)
146
- [200, { "Content-Type" => "application/json" }, [user.to_json]]
147
- }
148
- ```
149
-
150
151
  ## How It Works
151
152
 
152
153
  1. Extracts the `Authorization: Bearer <token>` header
@@ -194,26 +195,7 @@ Verikloak returns JSON error responses in a consistent format with structured er
194
195
  }
195
196
  ```
196
197
 
197
- ```json
198
- {
199
- "error": "jwks_parse_failed",
200
- "message": "Failed to parse JWKs"
201
- }
202
- ```
203
-
204
- ```json
205
- {
206
- "error": "discovery_metadata_fetch_failed",
207
- "message": "Failed to fetch OIDC discovery document"
208
- }
209
- ```
210
-
211
- ```json
212
- {
213
- "error": "discovery_metadata_invalid",
214
- "message": "Failed to parse OIDC discovery document"
215
- }
216
- ```
198
+ For a full list of error cases and detailed explanations, please see the [ERRORS.md](ERRORS.md) file.
217
199
 
218
200
  ### Error Types
219
201
 
@@ -223,30 +205,29 @@ Verikloak returns JSON error responses in a consistent format with structured er
223
205
  | `expired_token` | 401 Unauthorized | The token has expired |
224
206
  | `missing_authorization_header` | 401 Unauthorized | The `Authorization` header is missing |
225
207
  | `invalid_authorization_header` | 401 Unauthorized | The `Authorization` header format is invalid |
226
- | `unsupported_algorithm` | 401 Unauthorized | The tokens signing algorithm is not supported |
208
+ | `unsupported_algorithm` | 401 Unauthorized | The token's signing algorithm is not supported |
227
209
  | `invalid_signature` | 401 Unauthorized | The token signature could not be verified |
228
210
  | `invalid_issuer` | 401 Unauthorized | Invalid `iss` claim |
229
211
  | `invalid_audience` | 401 Unauthorized | Invalid `aud` claim |
230
212
  | `not_yet_valid` | 401 Unauthorized | The token is not yet valid (`nbf` in the future) |
231
- | `jwks_fetch_failed` | 503 Service Unavailable | Failed to fetch JWKs |
232
- | `jwks_parse_failed` | 503 Service Unavailable | Failed to parse JWKs |
213
+ | `jwks_fetch_failed` | 503 Service Unavailable | Failed to fetch JWKs |
214
+ | `jwks_parse_failed` | 503 Service Unavailable | Failed to parse JWKs |
233
215
  | `jwks_cache_miss` | 503 Service Unavailable | JWKs cache is empty (e.g., 304 Not Modified without prior cache) |
234
216
  | `discovery_metadata_fetch_failed` | 503 Service Unavailable | Failed to fetch OIDC discovery document |
235
- | `discovery_metadata_invalid` | 503 Service Unavailable | Failed to parse OIDC discovery document |
217
+ | `discovery_metadata_invalid` | 503 Service Unavailable | Failed to parse OIDC discovery document |
236
218
  | `discovery_redirect_error` | 503 Service Unavailable | Discovery response was a redirect without a valid Location header |
237
219
  | `internal_server_error` | 500 Internal Server Error | Unexpected internal error (catch-all) |
238
220
 
239
221
  > **Note:** The `decode_with_public_key` method ensures consistent error codes for all JWT verification failures.
240
222
  > It may raise `invalid_signature`, `unsupported_algorithm`, `expired_token`, `invalid_issuer`, `invalid_audience`, or `not_yet_valid` depending on the verification outcome.
241
223
 
242
- For a full list of error cases and detailed explanations, please see the [ERRORS.md](ERRORS.md) file.
243
-
244
224
  ## Configuration Options
245
225
 
246
226
  | Key | Required | Description |
247
227
  | --------------- | -------- | ------------------------------------------- |
248
228
  | `discovery_url` | Yes | Full URL to your realm's OIDC discovery doc |
249
229
  | `audience` | Yes | Your client ID (checked against `aud`). Accepts a String or callable returning a String/Array per request. |
230
+ | `issuer` | No | Optional override for the expected `iss` claim; defaults to the discovery `issuer`. |
250
231
  | `skip_paths` | No | Array of paths or wildcards to skip authentication, e.g. `['/', '/health', '/public/*']`. **Note:** Regex patterns are not supported. |
251
232
  | `discovery` | No | Inject custom Discovery instance (advanced/testing) |
252
233
  | `jwks_cache` | No | Inject custom JwksCache instance (advanced/testing) |
@@ -254,6 +235,10 @@ For a full list of error cases and detailed explanations, please see the [ERRORS
254
235
  | `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. |
255
236
  | `decoder_cache_limit` | No | Maximum number of `TokenDecoder` instances retained per middleware. Defaults to `128`. Set to `0` to disable caching or `nil` for an unlimited cache. |
256
237
  | `connection` | No | Inject a Faraday::Connection used for both Discovery and JWKs fetches. Defaults to a safe connection with timeouts and retries. |
238
+ | `token_env_key` | No | Rack env key for the raw JWT. Defaults to `verikloak.token`. |
239
+ | `user_env_key` | No | Rack env key for decoded claims. Defaults to `verikloak.user`. |
240
+ | `realm` | No | Value used in the `WWW-Authenticate` header. Defaults to `verikloak`. |
241
+ | `logger` | No | Logger for unexpected internal failures (responds to `error`, optionally `debug`). |
257
242
 
258
243
  #### Option: `skip_paths`
259
244
 
@@ -273,7 +258,7 @@ skip_paths: ['/', '/health', '/rails/*', '/public/src']
273
258
  Paths **not matched** by any `skip_paths` entry will require a valid JWT.
274
259
 
275
260
  **Note:** Regex patterns are not supported. Only literal paths and `*` wildcards are allowed.
276
- 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.
261
+ 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.
277
262
 
278
263
  #### Option: `audience`
279
264
 
@@ -343,6 +328,8 @@ config.middleware.use Verikloak::Middleware,
343
328
  - `token_verify_options:` is passed directly to TokenDecoder (and ultimately to `JWT.decode`).
344
329
  - If both are set, `token_verify_options[:leeway]` takes precedence.
345
330
 
331
+ ## Performance & Caching
332
+
346
333
  #### Decoder cache & performance
347
334
 
348
335
  Internally, Verikloak caches `TokenDecoder` instances per JWKs fetch to avoid reinitializing
@@ -489,9 +489,14 @@ module Verikloak
489
489
  previous_keys_id = cached_keys_identity(@jwks_cache)
490
490
  if @jwks_cache.nil?
491
491
  config = @discovery.fetch!
492
- @issuer = config['issuer']
492
+ # Use configured issuer if provided, otherwise use discovered issuer
493
+ @issuer = @configured_issuer || config['issuer']
493
494
  jwks_uri = config['jwks_uri']
494
495
  @jwks_cache = JwksCache.new(jwks_uri: jwks_uri, connection: @connection)
496
+ elsif @configured_issuer.nil? && @issuer.nil?
497
+ # If jwks_cache was injected but no issuer configured and not yet discovered, fetch discovery to set issuer
498
+ config = @discovery.fetch!
499
+ @issuer = config['issuer']
495
500
  end
496
501
 
497
502
  @jwks_cache.fetch!
@@ -633,6 +638,7 @@ module Verikloak
633
638
  # @param discovery_url [String] OIDC discovery endpoint URL
634
639
  # @param audience [String, #call] Expected `aud` claim. When a callable is provided it
635
640
  # receives the Rack env and may return a String or Array of audiences.
641
+ # @param issuer [String, nil] Optional issuer override (defaults to discovery `issuer`)
636
642
  # @param skip_paths [Array<String>] literal paths or wildcard patterns to bypass auth
637
643
  # @param discovery [Discovery, nil] custom discovery instance (for DI/tests)
638
644
  # @param jwks_cache [JwksCache, nil] custom JWKs cache instance (for DI/tests)
@@ -648,6 +654,7 @@ module Verikloak
648
654
  def initialize(app,
649
655
  discovery_url:,
650
656
  audience:,
657
+ issuer: nil,
651
658
  skip_paths: [],
652
659
  discovery: nil,
653
660
  jwks_cache: nil,
@@ -667,7 +674,10 @@ module Verikloak
667
674
  @leeway = leeway
668
675
  @token_verify_options = token_verify_options || {}
669
676
  @decoder_cache_limit = normalize_decoder_cache_limit(decoder_cache_limit)
670
- @issuer = nil
677
+ # Optional user-configured issuer (overrides discovery issuer when provided)
678
+ @configured_issuer = issuer
679
+ # Effective issuer; may be nil initially and set via discovery if not configured
680
+ @issuer = @configured_issuer
671
681
  @mutex = Mutex.new
672
682
  @decoder_cache = {}
673
683
  @decoder_cache_order = []
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Verikloak
4
4
  # Defines the current version of the Verikloak gem.
5
- VERSION = '0.2.1'
5
+ VERSION = '0.3.0'
6
6
  end
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.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - taiyaky
@@ -109,7 +109,7 @@ metadata:
109
109
  source_code_uri: https://github.com/taiyaky/verikloak
110
110
  changelog_uri: https://github.com/taiyaky/verikloak/blob/main/CHANGELOG.md
111
111
  bug_tracker_uri: https://github.com/taiyaky/verikloak/issues
112
- documentation_uri: https://rubydoc.info/gems/verikloak/0.2.1
112
+ documentation_uri: https://rubydoc.info/gems/verikloak/0.3.0
113
113
  rubygems_mfa_required: 'true'
114
114
  rdoc_options: []
115
115
  require_paths: