Keycloak SDK for Ruby
Authentication (OIDC / OAuth2) and the Admin REST API for Keycloak behind one consistent facade, with hardened JWT validation.
Part of a nine-language polyglot SDK (Java · Python · Node · Go · C# · PHP · Rust · Ruby · Kotlin) — one API surface, isomorphic across all of them: github.com/xzawed/KeyCloakSDK.
1.0.0is on RubyGems — the first release carrying the stability guarantee. A baregem install keycloak-sdkresolves it.
Requirements
- Ruby 3.2+
- Sync-only (every wrapped gem is synchronous); exception-based error handling under
KeycloakSdk::Error
Install
gem install keycloak-sdk
Or in a Gemfile:
gem "keycloak-sdk", "~> 1.0"
~> 1.0 accepts every 1.x release and stops before 2.0 — the boundary a breaking change has to cross under the promise below.
Name mismatch, on purpose: the gem is
keycloak-sdk(hyphen) but the require path and module arekeycloak_sdk/KeycloakSdk(underscore) — this avoids colliding with the existingkeycloakgem'sKeycloakmodule.
Quickstart
require "keycloak_sdk"
config = KeycloakSdk::Config.new(
server_url: "https://kc.example.com",
realm: "myrealm",
client_id: "admin-cli",
client_secret: "changeme" # load the real value from an env var / secrets manager
)
client = KeycloakSdk::KeycloakClient.new(config)
# 1) Issue a token via the client-credentials grant. TokenSet#inspect masks every token value.
token = client.auth.client_credentials_token
# 2) Validate it — algorithm pinning, exact iss, aud containment, mandatory exp, nbf, clock skew.
validated = client.auth.validate(token.access_token)
puts "subject=#{validated.subject} aud=#{validated.audience}"
# 3) Admin API — admin is created lazily on first access. create() returns the new user id.
user_id = client.admin.users.create({ username: "alice", enabled: true })
client.admin.users.delete(user_id)
client.close
Audience: validation requires the token's
audto containclient_id. A stock realm does not put the client id in a client-credentials token'saud, so on a default realm either passexpected_audience: "my-api"(the audience your realm actually issues), or add an Audience protocol mapper to the client in Keycloak.
The five admin resources — users / clients / roles / groups / realms — offer symmetric CRUD, and client.admin.raw is the escape hatch to the underlying bearer-authenticated Faraday::Connection.
Security defaults
The SDK replaces the unsafe library defaults rather than inheriting them:
- Algorithm pinning — the header-supplied
algis never trusted, soalg: noneand HS/RS confusion are rejected structurally: the pin is applied before key lookup and signature verification, not after. - Strict claim checks — exact
issmatch,audcontainment, mandatoryexp,nbf, and a bounded clock skew. - DoS-safe JWKS — a refetch is triggered only by an unresolved key ID and never by a bad signature, and is rate-limited to a minimum interval (
jwks_min_refetch, 30s by default). The gate applies on a cold cache too, so it cannot be sidestepped by hitting the SDK before its first successful fetch — no volume of forged tokens makes the SDK issue more than one JWKS request per interval. - OIDC nonce /
id_tokenreplay protection —create_authorization_requestalways issues a nonce (same default asstate:) and puts it on the authorization URL. Pass it back asexchange_code(expected_nonce:)and the SDK fully validates theid_tokenbefore comparing the nonce claim. Omitexpected_nonce:and id_token validation is skipped (same opt-out as the other eight languages). - Secret handling —
Config,TokenSet, andAuthorizationRequestmask secrets and tokens in#inspect(***, no prefix leak), TLS verification is on by default, timeouts are always applied, and redirect-following middleware is never installed (SSRF hardening).
Masking covers this SDK's own #inspect; it cannot cover what your logging framework or a backtrace does with a value you hand it. Ruby has no erasable string type, so the client secret lives in an ordinary String for its lifetime — masking is defence in depth, not an erasure guarantee.
Versioning and support
This SDK is 1.0 and follows SemVer: a breaking change to the public API requires a major bump. That promise is machine-backed — CI diffs this lane's public API against the previously published artifact on every build (yard diff), and a removal or an incompatible change fails the build. ⚠️ This lane’s gate is narrower than its siblings’: Ruby has no equivalent of japicmp/gorelease, so the gate catches a removed public object but not a changed method signature — that case is caught by review, not by machine. ⚠️ The gate compares the API surface. A change that leaves the surface identical but alters behaviour is not caught by it, so read the release notes before upgrading.
Only the newest released version of each language SDK receives security fixes; there are no long-term-support lines and older releases are not backported to.
Each of the nine languages versions independently. All nine reached 1.0.0 on the same day because they earned the same guarantee at the same time — they do not move in lockstep afterwards.
Documentation
- Project overview — all nine languages, what is identical and what is not
- Changelog — read this before upgrading; breaking changes are listed per language
- Getting started — install and quickstart for this language
- Compatibility — which Keycloak server range and base libraries each published version shipped against
- Deploying a Keycloak server — the server this SDK talks to
- Security policy
- Full example:
examples/quickstart.rb