keycloak-api-rails 2.0.3 → 2.0.4

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: 75cb985d142693a50337445bdde2608ee636c3ce77327e577a88f1d3ad6f12d7
4
- data.tar.gz: a9220359016e6c96f594d2e43485bb2e5c2821da9d2055c6c622eef26fcdf33f
3
+ metadata.gz: e074e3c78c789139c8eeaf6a127dceaf0c7ff7235c1134f3fa0ea3c4149b277a
4
+ data.tar.gz: 6b4a5b9faaa16ab883a25c03e5eb31a0c8740f80b784812d6f47eb7aae117eb3
5
5
  SHA512:
6
- metadata.gz: fab532886c13b4e2b950ff31b591d9670b0d8a343e835afa8922b35cd742142f63a6943685332d9ad5baf1146344dd6ff452209793f8dc7864c5a3322a520fad
7
- data.tar.gz: 0c8110fc856d3e74e49fb675749fcdac113808d140a15c29c01838abcd760ef10ff78527aec128e5c7eaa2c8d7d879ef1ebe111972fd1d0e8671ad188c2f638c
6
+ metadata.gz: 9472f08064d6a3e3ba64b781f089d2a42861b26323f9eba3a752ec63d41e54f32288b82ed0fa1b12e46418e9c1771351c7be8676476303162e88a28d676655bd
7
+ data.tar.gz: 0f106f193ceaa023611b9128910b5f944ebfe213c7b7fc1237f7a2e49c55c9b76564ac84ef080e3a4c36f48bcb463da434756d00af5f370cc07a5e8842c5c0f6
data/CHANGELOG.md CHANGED
@@ -5,7 +5,13 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [2.0.4] - 2026-08-03
9
+
10
+ ### Added
11
+
12
+ * New `issuer_url` option: the `iss` claim is checked against it instead of `server_url` when set, for setups where the API reaches Keycloak through a different address than the one Keycloak advertises to its clients (e.g. an internal Docker hostname vs. a public one).
13
+
14
+ ## [2.0.3] - 2026-08-03
9
15
 
10
16
  ### Security
11
17
 
data/README.md CHANGED
@@ -18,7 +18,7 @@ the recommended path.
18
18
  ## Install
19
19
 
20
20
  ```ruby
21
- gem "keycloak-api-rails", "2.0.3"
21
+ gem "keycloak-api-rails", "2.0.4"
22
22
 
23
23
  ## Token validation
24
24
 
@@ -60,6 +60,7 @@ All options have a default value. However, all of them can be changed in your in
60
60
  | Option | Default Value | Type | Required? | Description | Example |
61
61
  | ---- | ----- | ------ | ----- | ------ | ----- |
62
62
  | `server_url` | `nil`| String | Required | The base url where your Keycloak server is located, scheme included. Without one, the url cannot be parsed and no public key is ever downloaded | `https://keycloak.example.org` or `http://auth:8080` |
63
+ | `issuer_url` | `server_url`| String | Optional | The base url expected in a token's `iss` claim, when it differs from `server_url`. Needed when the API reaches Keycloak through a different address than the one Keycloak advertises to its clients, e.g. an internal Docker hostname vs. a public one | `https://keycloak.example.org` |
63
64
  | `realm_id` | `nil`| String, Array or Proc | Required | Realm's name(s) (not id, actually). A single String, an Array of Strings for multiple tenants, or a Proc receiving the realm named by the token and answering whether it is allowed | `"master"`, `["tenant1", "tenant2"]` or `->(realm) { Tenant.exists?(name: realm) }` |
64
65
  | `logger` | `Logger.new(STDOUT)`| Logger | Optional | The logger used by `keycloak-api-rails` | `Rails.logger` |
65
66
  | `skip_paths` | `{}`| Hash of methods and paths regexp | Optional | Paths whose token must not be validated, matched against the URL-decoded path of the request. Each path must be a `Regexp`, anchored with `\A` and `\z`: a String is refused when the application boots, and `^` and `$` are warned about, for the reasons given below | `{ get: [/\A\/health\/.+/] }`|
@@ -136,14 +137,26 @@ Keycloak only adds an API to the `aud` claim of a token once that API is declare
136
137
  `config.verify_not_before = true` additionally rejects a token whose `nbf` claim is in the future.
137
138
  It is disabled by default because a clock skew between Keycloak and the API rejects valid tokens.
138
139
 
139
- The `iss` claim is checked against `server_url` only when one is configured. A test environment replacing the public key resolver, as `keycloak-api-rails/testing` does, accepts any issuer.
140
+ The `iss` claim is checked against `issuer_url`, or `server_url` when `issuer_url` is not configured, only when one of the two is set. A test environment replacing the public key resolver, as `keycloak-api-rails/testing` does, accepts any issuer.
141
+
142
+ ## When Keycloak is reached through a different address than its own
143
+
144
+ `server_url` is used both to download public keys and, absent `issuer_url`, to check the `iss` claim. Those are two different needs: the former must be an address the API can reach, the latter must match what Keycloak put in the token, which is whatever address its own clients used. Behind a reverse proxy, or from inside Docker, these are often not the same:
145
+
146
+ ```ruby
147
+ KeycloakApiRails.configure do |config|
148
+ config.server_url = "http://keycloak:8080" # reachable from the API container
149
+ config.issuer_url = "https://keycloak.example.org" # what Keycloak's own hostname config puts in `iss`
150
+ config.realm_id = ENV["KEYCLOAK_REALM_ID"]
151
+ end
152
+ ```
140
153
 
141
154
  ## Multi-tenancy (Multiple Realms)
142
155
 
143
156
  The library natively supports multi-tenancy by validating tokens issued by multiple Keycloak realms.
144
157
  Instead of a static string, you can configure `realm_id` with an Array or a Proc. The library will dynamically extract the realm from the token's `iss` claim, check if it is permitted, and fetch the appropriate public keys for that specific realm.
145
158
 
146
- Those realms all live under the same `server_url`: a token whose `iss` names another server is rejected, however allowed the realm it names.
159
+ Those realms all live under the same `server_url` (or `issuer_url`, when configured): a token whose `iss` names another server is rejected, however allowed the realm it names.
147
160
  Declaring `expected_audience` matters all the more here — without it, a token issued for any client of any allowed realm opens the API.
148
161
 
149
162
  Using an Array of allowed realms:
@@ -344,8 +357,8 @@ scoped RubyGems credential.
344
357
  3. Tag the commit and push the tag:
345
358
 
346
359
  ```
347
- $ git tag -a v2.0.3 -m "Version 2.0.3"
348
- $ git push origin v2.0.3
360
+ $ git tag -a v2.0.4 -m "Version 2.0.4"
361
+ $ git push origin v2.0.4
349
362
  ```
350
363
 
351
364
  The workflow then checks that the tag matches `KeycloakApiRails::VERSION`, runs the tests, builds the gem
@@ -7,6 +7,7 @@ module KeycloakApiRails
7
7
  LOGGER_METHODS = [:debug, :info, :warn, :error].freeze
8
8
 
9
9
  attr_accessor :server_url
10
+ attr_accessor :issuer_url
10
11
  attr_accessor :realm_id
11
12
  attr_accessor :skip_paths
12
13
  attr_accessor :opt_in
@@ -27,6 +28,7 @@ module KeycloakApiRails
27
28
  errors = []
28
29
 
29
30
  errors.push("'server_url' must be a String or nil, got #{server_url.inspect}") unless server_url.nil? || server_url.is_a?(String)
31
+ errors.push("'issuer_url' must be a String or nil, got #{issuer_url.inspect}") unless issuer_url.nil? || issuer_url.is_a?(String)
30
32
  errors.push("'realm_id' must be a String, an Array of Strings, a Proc, or nil, got #{realm_id.inspect}") unless valid_realm_id?(realm_id)
31
33
  errors.push("'logger' must respond to #{LOGGER_METHODS.join(', ')}") unless LOGGER_METHODS.all? { |method| logger.respond_to?(method) }
32
34
  errors.push("'opt_in' must be true or false, got #{opt_in.inspect}") unless boolean?(opt_in)
@@ -116,8 +116,9 @@ module KeycloakApiRails
116
116
  raise TokenError.invalid_audience(token) unless audience_valid?(decoded_token)
117
117
  raise TokenError.invalid_token_type(token) unless token_type_valid?(decoded_token)
118
118
 
119
- if KeycloakApiRails.config.server_url
120
- expected_iss = File.join(KeycloakApiRails.config.server_url.to_s, "realms", realm_id.to_s)
119
+ issuer_url = KeycloakApiRails.config.issuer_url || KeycloakApiRails.config.server_url
120
+ if issuer_url
121
+ expected_iss = File.join(issuer_url.to_s, "realms", realm_id.to_s)
121
122
  raise TokenError.invalid_realm(token) unless decoded_token["iss"] == expected_iss
122
123
  end
123
124
  end
@@ -90,7 +90,8 @@ module KeycloakApiRails
90
90
  unless claims.key?(:iss) || claims.key?("iss")
91
91
  config_realm_id = KeycloakApiRails.config.realm_id
92
92
  realm_id = config_realm_id.is_a?(String) ? config_realm_id : "master"
93
- payload["iss"] = File.join(KeycloakApiRails.config.server_url.to_s, "realms", realm_id)
93
+ issuer_url = KeycloakApiRails.config.issuer_url || KeycloakApiRails.config.server_url
94
+ payload["iss"] = File.join(issuer_url.to_s, "realms", realm_id)
94
95
  end
95
96
 
96
97
  claims.each { |name, value| payload[name.to_s] = value }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KeycloakApiRails
4
- VERSION = "2.0.3"
4
+ VERSION = "2.0.4"
5
5
  end
@@ -78,6 +78,7 @@ module KeycloakApiRails
78
78
  def self.load_configuration
79
79
  configure do |config|
80
80
  config.server_url = nil
81
+ config.issuer_url = nil
81
82
  config.realm_id = nil
82
83
  config.logger = ::Logger.new(STDOUT)
83
84
  config.skip_paths = {}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keycloak-api-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.3
4
+ version: 2.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorent Lempereur