One SDK shape for Keycloak, in all nine languages. Issue a token, validate it safely, and drive the Admin REST API — with the same concepts, layers and flows whether the service in front of you is Java, Python, Node, Go, C#, PHP, Rust, Ruby or Kotlin.
Who it's for: teams running Keycloak behind services written in more than one language, who would rather not re-learn a client — and re-decide JWT validation — in every stack.
English · 한국어
"Polyglot" here means programming languages, not natural-language localization (i18n).
⚠️ All nine languages have shipped a stable release, and the numbers differ — Node and Python are on0.2.1, PHP is on0.2.0, and the other six are on0.1.0. Versions move per language, not as a fleet, so a language only gets a new number when something consumer-visible changed in it. Every release is human-gated, and the line is still pre-1.0. Install commands are in Install; a throwaway server to point them at is in Try it today.
You still do — this SDK wraps the best client in each ecosystem instead of replacing it, and adds the three things those clients leave to you:
- JWT validation that is hardened by default. The wrapped libraries ship permissive defaults, or only building blocks. Here, algorithm pinning, exact
iss,audchecking, mandatoryexp, bounded clock skew and rate-limited JWKS refetch are the default in all nine (.NET’s refetch trigger is broader — details below). - One mental model across the fleet. The same
auth/adminfacade, the same token and validation types, the same error hierarchy, in every language — so the Go service and the PHP service review the same way. - Nothing taken away. The wrapped client stays reachable through a
rawescape hatch whenever the facade doesn't cover what you need.
Every language follows the same three steps — get a token → validate it → call the Admin API. In Python:
from keycloak_sdk import KeycloakClient, KeycloakConfig
config = KeycloakConfig(
server_url="https://keycloak.example.com",
realm="my-realm",
client_id="my-client",
client_secret="…", # load from env / a secret manager
)
with KeycloakClient.create(config) as kc:
token = kc.auth.client_credentials_token() # 1. get a token
claims = kc.auth.validate(token.access_token) # 2. verify it (hardened)
users = kc.admin.users.search(first=0, max=10) # 3. call the Admin APIvalidate() requires the token's aud to contain the audience you expect — your client id by default. A token minted for a different audience is rejected until that expected audience is configured (in the SDK, or via an audience mapper in Keycloak). The same three steps in the other eight languages, plus async variants, are in the getting-started guide.
pip install keycloak-sdk # Python
npm install @xzawed/keycloak-sdk # Node
go get github.com/xzawed/KeyCloakSDK/go@v0.1.0 # Go
dotnet add package Xzawed.Keycloak.Sdk # C# / .NET
composer require xzawed/keycloak-sdk # PHP
cargo add keycloak-sdk # Rust
gem install keycloak-sdk # RubyJVM — add the coordinate to your build file:
io.github.xzawed:keycloak-sdk:0.1.0 # Java (Maven Central)
io.github.xzawed:keycloak-sdk-kotlin:0.1.0 # Kotlin (Maven Central)
Full snippets per build tool (Maven XML, Gradle Kotlin DSL, Gemfile, Cargo.toml) are in the getting-started guide.
You need a Keycloak server to talk to — this is a client library, and the server is a separate product:
# 1) a Keycloak server to talk to
docker run -p 8080:8080 \
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:26.6 start-devThen create a confidential client with its service account enabled in the realm — that pair is the client_id / client_secret the examples take. For a production server rather than a throwaway container, see deploying a Keycloak server.
To develop against the SDK itself rather than a released version, install from a clone — Python shown, every language has an equivalent in the getting-started guide:
git clone https://github.com/xzawed/KeyCloakSDK.git
pip install -e KeyCloakSDK/python| Language | Runtime · idiom | Package (all on public registries) | Package README | Example |
|---|---|---|---|---|
| Java | JDK 21+ · blocking | io.github.xzawed:keycloak-sdk (Maven Central) |
java/README.md | QuickStart.java |
| Python | 3.10+ · sync + async (aio) |
keycloak-sdk (PyPI) |
python/README.md | quickstart.py · async |
| Node | 22+ · ESM · async-only | @xzawed/keycloak-sdk (npm) |
node/README.md | quickstart.ts |
| Go | 1.25+ · sync + context.Context |
github.com/xzawed/KeyCloakSDK/go |
go/README.md | example_test.go |
| C# / .NET | 8+ · async-first | Xzawed.Keycloak.Sdk (NuGet) |
dotnet/README.md | getting-started |
| PHP | 8.3+ · final readonly class |
xzawed/keycloak-sdk (Packagist) |
php/README.md | quickstart.php |
| Rust | 1.88+ (edition 2024) · async (tokio) | keycloak-sdk (crates.io) |
rust/README.md | quickstart.rs |
| Ruby | 3.2+ · sync-only | keycloak-sdk (RubyGems) |
ruby/README.md | quickstart.rb |
| Kotlin | 2.2+ / JDK 21+ · coroutines | io.github.xzawed:keycloak-sdk-kotlin (Maven Central) |
kotlin/README.md | quickstart.kt |
- Auth: the same seven operations exist in all nine — client-credentials token · authorization request (PKCE S256) · code exchange · refresh · introspect · logout · validate. Signatures are not identical: PHP and Rust take
redirectUrifrom config rather than as an argument; Python names the startauthorization_url. All nine issue a nonce on the authorization request;exchange*validates theid_tokenagainst it when the caller passes that nonce back (the parameter is optional on all nine — omit it and id_token validation is skipped). The types are namedTokenSet/ValidatedToken/IntrospectionResulteverywhere; a few fields differ (expires_inis not on Java/Python/Kotlin; PHP and Ruby treat a missingexpiresAtas not-expired). Go and Rust return error values; the other seven raise aKeycloak*hierarchy. - Admin: five resources everywhere, and now the same five operations. All nine expose users · clients · realms · roles · groups with create · get · list · update · delete — 25/25 in every language — plus a
rawescape hatch for anything past that. What still differs is shape, not coverage: Rust's facade is flat (admin.update_role(name, rep)) where the rest nest (admin.roles().update(…)), and list pagination is explicit in Rust and Go. The exact table is the Admin capability matrix.
All nine ship the same claim-check rules (algorithm pinning, exact iss, aud containment, mandatory exp, bounded clock skew) — not the underlying library defaults. JWKS refetch is rate-limited on all nine; “only on an unresolved key id” is eight languages. .NET’s Microsoft.IdentityModel ConfigurationManager also refetches on a bad signature, so the rate-limit is the amplification cap — see dotnet/README.md.
Secrets and tokens are masked in logs and serialization, and TLS verification is on by default. Auth-path types stay behind the facade; admin representations and raw() are documented exceptions (Admin capability matrix).
All nine SDKs are feature-complete and merged to main. Each is verified against a real Keycloak 26.6 server (Testcontainers; PHP and Ruby shell out to the docker CLI). Logic modules are held to a line ≥ 90% coverage gate; six languages also gate branch ≥ 85% (Go, PHP and Rust measure lines only). Security cores were reviewed adversarially. Configurable JWT signature algorithms and dependency CVE audits apply across all nine. OIDC nonce / id_token replay protection is in all nine: create* always issues a nonce and puts it on the authorization URL; exchange* fully validates the id_token (signature · iss · aud · exp) and compares the nonce claim when the caller passes that value back. Omitting the nonce argument still skips id_token validation — that opt-out is the shared pattern, not a Ruby-only exception.
Everything is pre-1.0. All nine have shipped a stable release to a public registry, each behind a human tag gate, and each preceded by a release candidate that stays on its registry. Commands are under Install; the exact version each language shipped, with the libraries it wraps, is the compatibility table. See DEPLOY.md for the release procedure, and SECURITY.md for the security policy and what pre-1.0 means here.
- 🚀 Getting started — per-language install, runnable example, async variants
- 📐 Admin capability · Compatibility — what each language's admin facade covers, and what each published version shipped against
- 🖥️ Deploying a Keycloak server — the server your SDK connects to (single VM + Docker Compose)
- 🔒 Security policy — reporting, hardening scope, supported versions
- 🗺️ Language roadmap — what exists today and what may come next
- 📦 Deploy — the human-gated release procedure for all nine
Contributing: CONTRIBUTING.md · development setup (node scripts/doctor.mjs reports what your machine is missing) · add-a-language playbook · cross-language test harness. Internal architecture and maintainer notes live in CLAUDE.md; every design spec, plan and verification log is indexed in the documentation map.
License: Apache-2.0