verikloak 1.0.1 → 1.0.2
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 +11 -0
- data/README.md +15 -2
- data/lib/verikloak/token_decoder.rb +63 -5
- 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: f694670478e8bbeff2369e0898b96a2d675e052bc19c4ac8437bfb3ff209dc52
|
|
4
|
+
data.tar.gz: 8c189a0ae636ee82ab5e0589b047327c6bd5c8360d4a40f3fcbb6ebc1491743b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 456fb93fd6afd7376d4daedc1fe17c708d0b0c7055e5e215874418b84de69fbee048f70fc421560ae3e0296d264f5d4f0be1028382f28798531c292be7bc5ba1
|
|
7
|
+
data.tar.gz: 2f7f2107e34117cbc644b6b1c862da9521b9f1bda47a862ffb6747ad8a1bd851a3217ebc41113552684e1b67887074dfe845a14e6a0ae50cc7aaa6c4318a9665
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.0.2] - 2026-05-09
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **`iat` claim now honors `leeway`** (`TokenDecoder`): On ruby-jwt 3.x the built-in `Claims::IssuedAt` validator ignores the `leeway` option and raises `JWT::InvalidIatError` whenever `iat` is even a fraction of a second in the future. In typical OIDC deployments the IdP (e.g. Keycloak) and the Resource Server run on different hosts/containers, so `iat` is routinely a few hundred milliseconds to a few seconds ahead of the Resource Server's clock and the previously-effective leeway of `0` for `iat` produced spurious 401s. `TokenDecoder` now disables ruby-jwt's built-in `iat` check and performs its own `iat` validation that applies the configured `leeway` consistently with `exp` and `nbf`. Behaviour for tokens with `iat` further in the future than `leeway` is unchanged (still rejected with `invalid_token`). Pass `options: { verify_iat: false }` to skip the check entirely.
|
|
14
|
+
- **`iat` validation now handles symbol-keyed payloads**: `verify_iat_with_leeway!` looks up both `'iat'` and `:iat`, so callers who request symbol-keyed payloads from `JWT.decode` still get the leeway-aware `iat` check applied.
|
|
15
|
+
|
|
16
|
+
### Security
|
|
17
|
+
- **Bumped `rack` to `>= 3.2.6` and `json` to `>= 2.19.5`** in `Gemfile.lock` to clear known advisories surfaced by `bundler-audit` (rack request-smuggling and json format-string injection).
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
10
21
|
## [1.0.1] - 2026-02-15
|
|
11
22
|
|
|
12
23
|
### Fixed
|
data/README.md
CHANGED
|
@@ -336,8 +336,21 @@ config.middleware.use Verikloak::Middleware,
|
|
|
336
336
|
}
|
|
337
337
|
```
|
|
338
338
|
|
|
339
|
-
- `leeway:` sets the default skew tolerance in seconds.
|
|
340
|
-
|
|
339
|
+
- `leeway:` sets the default skew tolerance in seconds. It is applied to
|
|
340
|
+
`exp`, `nbf`, and `iat`. Verikloak applies leeway to `iat` itself
|
|
341
|
+
because ruby-jwt 3.x's built-in `iat` validator ignores the `leeway`
|
|
342
|
+
option; pass `token_verify_options: { verify_iat: false }` to skip the
|
|
343
|
+
`iat` check entirely.
|
|
344
|
+
- `token_verify_options:` is forwarded to `TokenDecoder` (and ultimately
|
|
345
|
+
to `JWT.decode`), with the following keys handled by Verikloak rather
|
|
346
|
+
than passed through verbatim:
|
|
347
|
+
- `:leeway` — Verikloak forwards it to `JWT.decode` so it applies to
|
|
348
|
+
`exp`/`nbf`, and also uses it for its own `iat` check.
|
|
349
|
+
- `:verify_iat` — ruby-jwt's built-in `iat` validator is always
|
|
350
|
+
disabled (it ignores `:leeway` on ruby-jwt 3.x). Setting
|
|
351
|
+
`verify_iat: false` instead skips Verikloak's own `iat` check;
|
|
352
|
+
setting `verify_iat: true` (the default) keeps it enabled.
|
|
353
|
+
All other keys are forwarded to `JWT.decode` as-is.
|
|
341
354
|
- If both are set, `token_verify_options[:leeway]` takes precedence.
|
|
342
355
|
|
|
343
356
|
## Performance & Caching
|
|
@@ -8,7 +8,9 @@ module Verikloak
|
|
|
8
8
|
# This class validates a JWT's signature and standard claims (`iss`, `aud`, `exp`, `nbf`, etc.)
|
|
9
9
|
# using the appropriate RSA public key selected by the JWT's `kid` header.
|
|
10
10
|
# Only `RS256`-signed tokens with RSA JWKs are supported.
|
|
11
|
-
# It also supports a configurable clock skew (`leeway`) to account for minor time drift
|
|
11
|
+
# It also supports a configurable clock skew (`leeway`) to account for minor time drift,
|
|
12
|
+
# which is applied uniformly to `exp`, `nbf`, and `iat` (Verikloak applies leeway to
|
|
13
|
+
# `iat` itself because ruby-jwt 3.x's built-in `iat` validator ignores leeway).
|
|
12
14
|
#
|
|
13
15
|
# @example
|
|
14
16
|
# decoder = Verikloak::TokenDecoder.new(
|
|
@@ -41,7 +43,12 @@ module Verikloak
|
|
|
41
43
|
@leeway = leeway
|
|
42
44
|
# Normalize and store verification options
|
|
43
45
|
@options = symbolize_keys(options || {})
|
|
44
|
-
|
|
46
|
+
# Remove keys we manage ourselves so user-supplied values don't
|
|
47
|
+
# re-enable ruby-jwt's built-in (broken w.r.t. leeway) iat validator
|
|
48
|
+
# via the final merge in #jwt_decode_options. The user's intent for
|
|
49
|
+
# :leeway / :verify_iat is still honoured by reading from @options
|
|
50
|
+
# directly elsewhere in this class.
|
|
51
|
+
@options_for_jwt = @options.except(:leeway, :verify_iat).freeze
|
|
45
52
|
|
|
46
53
|
# Build a kid-indexed hash for O(1) JWK lookup
|
|
47
54
|
@jwk_by_kid = {}
|
|
@@ -118,9 +125,49 @@ module Verikloak
|
|
|
118
125
|
# @return [Hash] Verified claims (payload).
|
|
119
126
|
def decode_with_public_key(token, public_key)
|
|
120
127
|
payload, = JWT.decode(token, public_key, true, jwt_decode_options)
|
|
128
|
+
verify_iat_with_leeway!(payload)
|
|
121
129
|
payload
|
|
122
130
|
end
|
|
123
131
|
|
|
132
|
+
# Verifies the `iat` (issued-at) claim with the configured leeway tolerance.
|
|
133
|
+
#
|
|
134
|
+
# ruby-jwt 3.x's built-in `Claims::IssuedAt` validator does not honor the
|
|
135
|
+
# `leeway` option; it raises `JWT::InvalidIatError` whenever `iat` is even
|
|
136
|
+
# a fraction of a second in the future. In OIDC deployments the IdP
|
|
137
|
+
# (e.g. Keycloak) and the Resource Server typically run on different
|
|
138
|
+
# hosts/containers with small clock skew, so `iat` is routinely a few
|
|
139
|
+
# hundred milliseconds to a few seconds in the future relative to the
|
|
140
|
+
# Resource Server's clock. To provide behaviour consistent with `exp`
|
|
141
|
+
# and `nbf` (which honor `leeway`), Verikloak performs its own `iat`
|
|
142
|
+
# check after `JWT.decode` with the configured leeway applied.
|
|
143
|
+
#
|
|
144
|
+
# Users may opt out by passing `options: { verify_iat: false }` to the
|
|
145
|
+
# decoder, in which case this check is skipped entirely.
|
|
146
|
+
#
|
|
147
|
+
# @param payload [Hash] Decoded JWT claims.
|
|
148
|
+
# @return [void]
|
|
149
|
+
# @raise [TokenDecoderError] code: 'invalid_token' when `iat` is not
|
|
150
|
+
# numeric or is further in the future than the allowed leeway.
|
|
151
|
+
def verify_iat_with_leeway!(payload)
|
|
152
|
+
return if @options[:verify_iat] == false
|
|
153
|
+
return unless payload.is_a?(Hash)
|
|
154
|
+
|
|
155
|
+
# Support both string- and symbol-keyed payloads. Callers may pass
|
|
156
|
+
# `options: { symbolize_names: true }` which causes JWT.decode to
|
|
157
|
+
# return symbol keys; without indifferent lookup we'd silently
|
|
158
|
+
# skip iat validation while ruby-jwt's check is disabled.
|
|
159
|
+
return unless payload.key?('iat') || payload.key?(:iat)
|
|
160
|
+
|
|
161
|
+
iat = payload.key?('iat') ? payload['iat'] : payload[:iat]
|
|
162
|
+
leeway = @options.key?(:leeway) ? @options[:leeway] : @leeway
|
|
163
|
+
leeway = leeway.to_f
|
|
164
|
+
|
|
165
|
+
return if iat.is_a?(Numeric) && iat.to_f <= Time.now.to_f + leeway
|
|
166
|
+
|
|
167
|
+
raise TokenDecoderError.new('Invalid issued-at (iat) claim',
|
|
168
|
+
code: 'invalid_token')
|
|
169
|
+
end
|
|
170
|
+
|
|
124
171
|
# Returns the verification options passed to JWT.decode.
|
|
125
172
|
#
|
|
126
173
|
# Enforces:
|
|
@@ -129,6 +176,15 @@ module Verikloak
|
|
|
129
176
|
# - Expiration (`exp`) and not-before (`nbf`) checks
|
|
130
177
|
# - Clock skew tolerance via `leeway`
|
|
131
178
|
#
|
|
179
|
+
# NOTE: `verify_iat` is intentionally disabled here. ruby-jwt 3.x's
|
|
180
|
+
# built-in `iat` validator ignores the `leeway` option, which causes
|
|
181
|
+
# spurious `JWT::InvalidIatError` failures whenever the IdP clock is
|
|
182
|
+
# even slightly ahead of the Resource Server's clock. Verikloak
|
|
183
|
+
# re-implements the `iat` check in {#verify_iat_with_leeway!} so the
|
|
184
|
+
# configured `leeway` is honoured consistently with `exp` and `nbf`.
|
|
185
|
+
# Users may still opt out entirely by passing
|
|
186
|
+
# `options: { verify_iat: false }` to the decoder.
|
|
187
|
+
#
|
|
132
188
|
# @return [Hash]
|
|
133
189
|
def jwt_decode_options
|
|
134
190
|
base = {
|
|
@@ -137,15 +193,17 @@ module Verikloak
|
|
|
137
193
|
verify_iss: true,
|
|
138
194
|
aud: @audience,
|
|
139
195
|
verify_aud: true,
|
|
140
|
-
|
|
196
|
+
# See note above: handled by verify_iat_with_leeway! after decode.
|
|
197
|
+
verify_iat: false,
|
|
141
198
|
verify_expiration: true,
|
|
142
199
|
verify_not_before: true
|
|
143
200
|
}
|
|
144
201
|
# options[:leeway] overrides top-level @leeway if provided
|
|
145
202
|
leeway = @options.key?(:leeway) ? @options[:leeway] : @leeway
|
|
146
203
|
merged = base.merge(leeway: leeway)
|
|
147
|
-
# Merge remaining options last (excluding :leeway
|
|
148
|
-
|
|
204
|
+
# Merge remaining options last (excluding :leeway and :verify_iat,
|
|
205
|
+
# which Verikloak manages itself — see initializer comment).
|
|
206
|
+
extra = @options_for_jwt
|
|
149
207
|
merged.merge(extra)
|
|
150
208
|
end
|
|
151
209
|
|
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: 1.0.
|
|
4
|
+
version: 1.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- taiyaky
|
|
@@ -131,7 +131,7 @@ metadata:
|
|
|
131
131
|
source_code_uri: https://github.com/taiyaky/verikloak
|
|
132
132
|
changelog_uri: https://github.com/taiyaky/verikloak/blob/main/CHANGELOG.md
|
|
133
133
|
bug_tracker_uri: https://github.com/taiyaky/verikloak/issues
|
|
134
|
-
documentation_uri: https://rubydoc.info/gems/verikloak/1.0.
|
|
134
|
+
documentation_uri: https://rubydoc.info/gems/verikloak/1.0.2
|
|
135
135
|
rubygems_mfa_required: 'true'
|
|
136
136
|
rdoc_options: []
|
|
137
137
|
require_paths:
|