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 +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +58 -71
- data/lib/verikloak/middleware.rb +12 -2
- data/lib/verikloak/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 24bc2b5eb7f699c9f8bc4970a2b0b6cdca87b7c7e37d9718c51d85d8cf58a8cc
|
|
4
|
+
data.tar.gz: 439ec2fb34a67140476aff243addfaac8cb8d5ac18ef7b6915d3a9ba6dc5947d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
119
|
+
### Standalone Rack app
|
|
88
120
|
|
|
89
121
|
```ruby
|
|
90
|
-
config.
|
|
91
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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 token
|
|
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
|
|
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
|
data/lib/verikloak/middleware.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 = []
|
data/lib/verikloak/version.rb
CHANGED
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.
|
|
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.
|
|
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:
|