Keycloak SDK for Python
Authentication (OIDC / OAuth2) and the Admin REST API for Keycloak behind one consistent facade, with hardened JWT validation and a full async mirror.
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.4is on PyPI — a barepip install keycloak-sdkresolves it. A patch release on top of1.0.3; no public API changed. Security: token and introspection responses — and the admin client's own token grant — are now read only up to 1 MiB (1,048,576 bytes, after gzip or deflate), sync andaioalike, and so is thelogoutresponse; a usable token padded with 32 MiB of JSON whitespace used to be held whole. The cap is 16 times the longest bearer a default Keycloak 26.6 accepts, so no token a server accepts is refused; a larger response raisesKeycloakAuthError(token response exceeds 1048576 bytesand the like), and the admin client sends no admin request. Requests to the token endpoint now carryAccept-Encoding: gzip, deflateonly — neverbrorzstd, even withbrotliorzstandardinstalled. The admin client now re-checks its hardening on the live connection before every request, so replacingraw.connectionno longer brings back the emptyAuthorization: Beareror lets the grant that carriesclient_secretfollow a redirect. An admin error whose body is not UTF-8 is aKeycloakAdminError(KeycloakNotFoundErrorfor a 404) instead of a rawUnicodeDecodeErrorwhose context carried the whole body, and the SDK now decompresses a JWKS response itself, stopping at the 51,200-byte cap (a body it cannot decompress is aKeycloakTransportError, not a rawzlib.error). ⚠️rawis now an instance of a subclass of its class —isinstanceand identity are unchanged, but an exact type comparison (type(raw) is KeycloakAdmin) is false, and an injected admin that cannot be adapted that way is refused at construction withKeycloakConfigError. Fixed: cancelling anaiovalidate()(orexchange_code()with a nonce), timeouts included, no longer spends the forced JWKS refetch window — the fetch runs to completion and fills the cache, so a rotated key's tokens are not refused for the rest of the window.1.0.0was the first release carrying the stability guarantee: a breaking change to the public API requires a major bump.⚠️ One breaking change since
0.1.0, and it is narrow (it landed in0.2.0;1.0.0adds none): thekeycloak_sdk.jwtmodule moved tokeycloak_sdk._internal.jwt. Only code that importedJwtValidatorfrom that path directly is affected — it was never in__all__and appears in no quickstart. The normal validation path,kc.auth.validate(token), is unchanged. The move was structural:py.typedmakes every module's signatures part of the public type surface, so a module under the top level was publishing joserfc'sKeySetas part of this SDK's API.
Requirements
- Python 3.10+
- Ships the PEP 561
py.typedmarker, so consumers can type-check withmypytoo
Install
pip install keycloak-sdk
The distribution name is keycloak-sdk; the import package is keycloak_sdk.
Quickstart
from keycloak_sdk import KeycloakClient, KeycloakConfig
config = KeycloakConfig(
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
)
# The `with` block closes both sessions on exit — the auth session and, since the admin facade owns its own `requests.Session` pair, those too (`AdminClient.close()` is not a no-op).
with KeycloakClient.create(config) as kc:
# 1) Issue a client-credentials token. repr(TokenSet) masks every token value.
token = kc.auth.client_credentials_token()
# 2) Validate it — algorithm pinning, exact iss, aud containment, mandatory exp, clock skew.
validated = kc.auth.validate(token.access_token)
print(f"subject={validated.subject} aud={validated.audience}")
# 3) Admin API — admin is created lazily on first access. create() returns the new user id.
user_id = kc.admin.users.create({"username": "alice", "enabled": True})
users = kc.admin.users.search(first=0, max=20)
validate() expects the token's aud to contain client_id by default, but a stock realm does not put the client id into a client-credentials token. Either set expected_audience="my-api" on the config to check the audience your tokens actually carry, or add an audience mapper to the client in Keycloak (Client scopes → dedicated scope → Add mapper → Audience). expected_audience applies to access tokens only: the id_token that exchange_code(..., nonce=...) verifies must always carry client_id in aud (OIDC Core §3.1.3.7).
Async
keycloak_sdk.aio is a complete async mirror — same method names (the sync close() is aclose() here), value types, and exceptions — so it never blocks the event loop (FastAPI and friends):
from keycloak_sdk import KeycloakConfig
from keycloak_sdk.aio import AsyncKeycloakClient
async def handler(config: KeycloakConfig) -> None:
async with AsyncKeycloakClient.create(config) as kc:
token = await kc.auth.client_credentials_token()
validated = await kc.auth.validate(token.access_token)
users = await kc.admin.users.search(first=0, max=20)
Only authorization_url stays synchronous — it assembles a URL and needs no network.
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: joserfc decodes against the configured allowlist, and an empty allowlist is refused at construction rather than falling back to joserfc's permissive default set. - Strict claim checks — exact
issmatch,audcontainment, mandatoryexp,nbfenforced when present, 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_seconds, 30s by default) — so once the key set has been fetched, no volume of forged tokens makes the SDK issue more than one JWKS request per interval. A failed fetch is bounded separately: consecutive failures back off exponentially (0.2s, doubling, capped at 5s, with jitter), and inside that window the SDK fails fast without contacting the IdP. So a cold cache during an IdP outage no longer turns every validation into a request (measured: 20 attempts → 1 request, down from 20; the sync and async clients share the same state machine). ⚠️ The SDK never sleeps — it returns the error immediately, so retry pacing stays the caller's decision. - Secret handling —
repr()of the config and token types masks secrets and tokens as***(no prefix leak), and TLS verification is on by default.
Masking covers this SDK's own repr(); it cannot cover what your logging framework or a traceback does with a value you hand it. Python has no erasable string type, so the client secret lives in an ordinary str 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 (griffe check), and a removal or an incompatible change fails the build. ⚠️ 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 in the same release wave 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 examples:
quickstart.py·async_quickstart.py
License
Metadata
Release files for keycloak-sdk 1.0.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| keycloak_sdk-1.0.4.tar.gz | 202.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| keycloak_sdk-1.0.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 284.8 kB
Release files / keycloak_sdk-1.0.4.tar.gz
| Download URL | keycloak_sdk-1.0.4.tar.gz |
|---|---|
| Size | 202.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
46a69e2ecd4ad80dea98f28839bfb114c75ac50102a37780a968cafec181c8a2
|
|
BLAKE2b-256 checksum How to use checksums |
d4b4a16a22072b3a80eeb4c46f39020520103dc628ff03deb3b293fe2be3d10c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.
Transparency logRelease files / keycloak_sdk-1.0.4-py3-none-any.whl
| Download URL | keycloak_sdk-1.0.4-py3-none-any.whl |
|---|---|
| Size | 82.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1fb663edccf0b341db8ca9bc56600242a9a1ebd16b12f524483634371e30c6e1
|
|
BLAKE2b-256 checksum How to use checksums |
d062239abdb127680ca55768cb71f1e01134a0ea1a274e90f3edf50b5b264514
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.
Transparency log