Async Keycloak client with automated token management
Project description
PyKeycloak
PyKeycloak is a library for working with Keycloak that provides asynchronous methods for authentication, token management, and permission handling.
What's Different from Other Libraries
- Sanitized logging: Automatically hide sensitive data in request/response logs.
- Httpx-powered: Gain full control using standard httpx client configuration.
- Rich Request/Response handling: Access a comprehensive list of parameters and detailed response fields.
- Flexible Data Access: Easily work with both raw data and structured representations.
- Environment-based config: Quick setup using environment variables.
Installation
To install dependencies for local development, use the following command:
make install
Development and Security Tooling
Runtime users of the library only install pykeycloak and its package dependencies.
For contributors (local checks + CI parity), install:
uvpre-commit
Then run:
make install
uv run pre-commit install
Security checks are executed in both pre-commit and GitHub Actions:
- Dependency CVE audit:
pip-audit --strict
Release Version Bump
Releases are tag-driven via GitHub Actions:
- Tag format:
vX.Y.Z(example:v0.7.4) - On tag push, CI syncs
pyproject.tomlversion from the tag before build/publish. - After successful publish, GitHub Release is created automatically with generated release notes.
- Build artifacts include a CycloneDX SBOM (
sbom.cyclonedx.json) attached to the GitHub Release. - CI enforces an SBOM license deny policy (fails on disallowed copyleft licenses by default).
- License policy is defined in
.license-policy.toml(deny = [...]).
Local helpers:
make release-bump # uses GITHUB_REF_NAME
make release-bump-tag TAG=v0.7.4 # explicit tag
Usage Examples
The library can be used in 3 different ways:
- Make requests directly through the client
- Use the provider to get a response with content
- Use the service to get either raw responses or Representation objects corresponding to the data received from Keycloak
MCP Server
This repository includes an MCP server: mcp_server.py.
Run MCP smoke test:
make mcp-smoke
Run server:
uv run python mcp_server.py
MCP runtime env vars:
MCP_TRANSPORT(stdio,sse, orstreamable-http; defaultstdio)MCP_HOST(default127.0.0.1)MCP_PORT(default8000)
Codex MCP config example (~/.codex/config.toml):
[mcp_servers.pykeycloak]
command = "uv"
args = ["run", "python", "mcp_server.py"]
cwd = "path/to/PyKeycloak"
[mcp_servers.pykeycloak.env]
MCP_TRANSPORT = "stdio"
MCP_HOST = "127.0.0.1"
MCP_PORT = "8000"
KEYCLOAK_BASE_URL = "http://127.0.0.1:8080"
Set cwd to your local repository path.
Main MCP tools:
healthkeycloak_registerkeycloak_register_from_envkeycloak_list_methodskeycloak_callkeycloak_close_all
MCP client configuration examples:
- Codex (
~/.codex/config.toml)
[mcp_servers.pykeycloak]
command = "uv"
args = ["run", "python", "mcp_server.py"]
cwd = "path/to/PyKeycloak"
[mcp_servers.pykeycloak.env]
MCP_TRANSPORT = "stdio"
KEYCLOAK_BASE_URL = "http://127.0.0.1:8080"
- Claude Desktop (
claude_desktop_config.json)
{
"mcpServers": {
"pykeycloak": {
"command": "uv",
"args": ["run", "python", "mcp_server.py"],
"cwd": "path/to/PyKeycloak",
"env": {
"MCP_TRANSPORT": "stdio",
"KEYCLOAK_BASE_URL": "http://127.0.0.1:8080"
}
}
}
}
- Cursor (MCP JSON config)
{
"mcpServers": {
"pykeycloak": {
"command": "uv",
"args": ["run", "python", "mcp_server.py"],
"cwd": "path/to/PyKeycloak",
"env": {
"MCP_TRANSPORT": "stdio",
"KEYCLOAK_BASE_URL": "http://127.0.0.1:8080"
}
}
}
}
- Cline (MCP JSON config)
{
"mcpServers": {
"pykeycloak": {
"command": "uv",
"args": ["run", "python", "mcp_server.py"],
"cwd": "path/to/PyKeycloak",
"env": {
"MCP_TRANSPORT": "stdio",
"KEYCLOAK_BASE_URL": "http://127.0.0.1:8080"
}
}
}
}
- Continue (MCP JSON config)
{
"mcpServers": {
"pykeycloak": {
"command": "uv",
"args": ["run", "python", "mcp_server.py"],
"cwd": "path/to/PyKeycloak",
"env": {
"MCP_TRANSPORT": "stdio",
"KEYCLOAK_BASE_URL": "http://127.0.0.1:8080"
}
}
}
}
After connecting your MCP client, test with this sequence:
healthkeycloak_register_from_env(orkeycloak_register)keycloak_list_methodskeycloak_call
Constants
Dynamic environment
## These variables are dependant on client name
##
## KEYCLOAK_REALM_{realm_client_name}_REALM_NAME
##
## When the instance attached to container it looking for environment variables
## KEYCLOAK_REALM_{realm_client_name}_CLIENT_ID
## KEYCLOAK_REALM_{realm_client_name}_CLIENT_SECRET
##
## pykeycloak_client.register(key, RealmClient.from_env(client_name=realm_client_name))
##
## But you don't need those when making RealmClient not from env
##
KEYCLOAK_REALM_OTAGO_SERVICE_REALM_NAME=
KEYCLOAK_REALM_OTAGO_SERVICE_CLIENT_UUID=
KEYCLOAK_REALM_OTAGO_SERVICE_CLIENT_ID=
KEYCLOAK_REALM_OTAGO_SERVICE_CLIENT_SECRET=
KEYCLOAK_REALM_OTAGO_SSO_REALM_NAME=
KEYCLOAK_REALM_OTAGO_SSO_CLIENT_UUID=
KEYCLOAK_REALM_OTAGO_SSO_CLIENT_ID=
KEYCLOAK_REALM_OTAGO_SSO_CLIENT_SECRET=
##
Default environment
KEYCLOAK_BASE_URL=
KEYCLOAK_HTTPX_CLIENT_PARAMS_HTTP1=
KEYCLOAK_HTTPX_CLIENT_PARAMS_HTTP2=
KEYCLOAK_HTTPX_CLIENT_PARAMS_SSL_VERIFY=
KEYCLOAK_HTTPX_CLIENT_PARAMS_FOLLOW_REDIRECTS=
KEYCLOAK_HTTPX_CLIENT_PARAMS_TRUST_ENV=
KEYCLOAK_HTTPX_CLIENT_PARAMS_TIMEOUT=
KEYCLOAK_HTTPX_CLIENT_PARAMS_MAX_CONNECTIONS=
KEYCLOAK_HTTPX_CLIENT_PARAMS_MAX_KEEPALIVE_CONNECTIONS=
KEYCLOAK_HTTPX_CLIENT_PARAMS_KEEPALIVE_EXPIRY=
KEYCLOAK_HTTPX_CLIENT_PARAMS_MAX_REDIRECTS=
KEYCLOAK_HTTPX_CLIENT_PARAMS_DEFAULT_ENCODING=utf-8
KEYCLOAK_MAX_ROWS_QUERY_LIMIT=1000
KEYCLOAK_HTTPX_HTTP_TRANSPORT_HTTP_VERIFY=
KEYCLOAK_HTTPX_HTTP_TRANSPORT_HTTP_CERT=
KEYCLOAK_HTTPX_HTTP_TRANSPORT_HTTP_TRUST_ENV=
KEYCLOAK_HTTPX_HTTP_TRANSPORT_HTTP_HTTP1=
KEYCLOAK_HTTPX_HTTP_TRANSPORT_HTTP_HTTP2=
KEYCLOAK_HTTPX_HTTP_TRANSPORT_HTTP_RETRIES=
KEYCLOAK_HTTPX_HTTP_TRANSPORT_HTTP_PROXY=
KEYCLOAK_HTTPX_HTTP_TRANSPORT_HTTP_UDS=
KEYCLOAK_HTTPX_HTTP_TRANSPORT_HTTP_LOCAL_ADDRESS=
KEYCLOAK_HTTPX_HTTP_TRANSPORT_HTTP_MAX_CONNECTIONS=
KEYCLOAK_HTTPX_HTTP_TRANSPORT_HTTP_KEEPALIVE_EXPIRY=
KEYCLOAK_HTTPX_HTTP_TRANSPORT_HTTP_MAX_KEEPALIVE_CONNECTIONS=
KEYCLOAK_HTTP_RETRY_ENABLED=true
KEYCLOAK_HTTP_RETRY_MAX_ATTEMPTS=3
KEYCLOAK_HTTP_RETRY_BASE_DELAY_SECONDS=0.2
KEYCLOAK_HTTP_RETRY_MAX_DELAY_SECONDS=2.0
KEYCLOAK_HTTP_RETRY_JITTER_SECONDS=0.1
KEYCLOAK_HTTP_RETRY_METHODS=GET,HEAD,OPTIONS,DELETE
DATA_SANITIZER_EXTRA_SENSITIVE_KEYS=
UMA_PERMISSIONS_CHUNK_SIZE=1
Initial start
from pykeycloak_client.pykeycloak import PyKeycloak
from pykeycloak_client.core.realm import RealmClient
key = "otago_service"
pkc = PyKeycloak()
pkc.register(key, RealmClient.from_env(client_name=key))
pkc.get(key)
Providers
KeycloakInMemoryProviderAsync- Asynchronous provider for working with Keycloak that provides methods for authentication, token refresh, user information retrieval, logout, token introspection, device authentication, and certificate retrieval.
Services
AuthService- authentication, token refresh, user information retrieval, logout, token introspection, device authentication, and certificate retrieval.UmaService- UMA permissions.UsersServiceRolesServiceSessionsServiceClientsServiceAuthzServiceAuthzResourceServiceAuthzScopeServiceAuthzPermissionServiceAuthzPolicyServiceWellKnownService
Core Entities
Payloads
-
TokenIntrospectionPayload- Payload for token introspection containing the token. -
RTPIntrospectionPayload- Payload for token introspection inherited fromTokenIntrospectionPayload, containing the token type. -
ObtainTokenPayload- Base class for obtaining a token, containing the scope and grant type. -
UserCredentialsLoginPayload- Payload for user authentication containing username and password. -
ClientCredentialsLoginPayload- Payload for client authentication used to obtain a client token. -
RefreshTokenPayload- Payload for refreshing a token containing the refresh token. -
UMAAuthorizationPayload- Payload for UMA authorization containing audience, permissions, and other parameters.
Representations
Representations duplicate the data from Keycloak documentation based on the actual values they return.
TokenRepresentation - Representation of a token containing information about the access token, expiration time, scope, and token type.
UserInfoRepresentation - Representation of user information containing user data such as first name, last name, email address, and other attributes.
RealmAccessRepresentation - Representation of realm access containing user roles in the realm.
IntrospectRepresentation - Representation of token introspection result containing token information such as audience, expiration time, token type, and other attributes.
Client
RealmClient - Entity that stores realm data:
import os
from pykeycloak_client.core.realm import RealmClient
## To get pre-configured client based on environment variables
RealmClient.from_env(client_name='random_client_name')
# or if you hande environment variables manually
RealmClient(
realm_name='realm_name',
client_id=os.getenv("KEYCLOAK_REALM_CLIENT_ID"),
client_uuid=os.getenv("KEYCLOAK_REALM_CLIENT_UUID"),
client_secret=os.getenv("KEYCLOAK_REALM_CLIENT_SECRET")
)
Sanitizer
Processes headers and request/response logs, hiding all critical information and marking it as hidden.
import os
from pykeycloak_client.core.sanitizer import SensitiveDataSanitizer
SensitiveDataSanitizer.from_env()
SensitiveDataSanitizer(
sensitive_keys=frozenset(os.getenv("DATA_SANITIZER_EXTRA_SENSITIVE_KEYS", None))
)
Client Initialization
To get started, you need to initialize the client using environment variables:
User Authentication
To authenticate a user, use the user_login_async method:
from pykeycloak_client.providers.payloads import UserCredentialsLoginPayload
from pykeycloak_client.pykeycloak import PyKeycloak
pkc = PyKeycloak()
# add client ....
token = await pkc.get('otago_client').auth.user_login_async(
payload=UserCredentialsLoginPayload(
username=username,
password=password,
))
Token Refresh
To refresh a token, use the refresh_token_async method:
from pykeycloak_client.pykeycloak import PyKeycloak
pkc = PyKeycloak()
# add client ....
token = await pkc.get('otago_client').auth.refresh_token_async(
payload=RefreshTokenPayload(refresh_token=token.refresh_token)
)
Integration Smoke Tests
Integration tests are disabled by default and run only against a real Keycloak instance.
Required environment variables:
PYKEYCLOAK_INTEGRATION_ENABLED=1
KEYCLOAK_BASE_URL=http://localhost:8080
KEYCLOAK_REALM_IT_REALM_NAME=master
KEYCLOAK_REALM_IT_CLIENT_UUID=<client-uuid>
KEYCLOAK_REALM_IT_CLIENT_ID=<client-id>
KEYCLOAK_REALM_IT_CLIENT_SECRET=<client-secret>
Optional variables for user login + UMA smoke test:
KEYCLOAK_IT_USERNAME=<username>
KEYCLOAK_IT_PASSWORD=<password>
KEYCLOAK_IT_UMA_PERMISSIONS=/resource#view,/resource#update
Run:
uv run pytest tests/integration -m integration -vv -s
CI has a manual compatibility matrix against Keycloak 24.0, 25.0, and 26.0 via GitHub Actions workflow_dispatch input (run_integration_matrix=true).
Token Introspection
To introspect a token, use the introspect_async method:
from pykeycloak_client.providers.payloads import TokenIntrospectionPayload
from pykeycloak_client.pykeycloak import PyKeycloak
pkc = PyKeycloak()
# add client ....
introspection = await pkc.get('otago_client').auth.introspect_token_async(
payload=TokenIntrospectionPayload(
token=refresh.auth_token,
)
)
UMA Permission Retrieval
To retrieve UMA permissions, use the get_uma_permissions_async method:
from pykeycloak_client.providers.payloads import UMAAuthorizationPayload
from pykeycloak_client.pykeycloak import PyKeycloak
pkc = PyKeycloak()
# add client ....
permissions = await pkc.get('otago_client').uma.get_uma_permissions_async(
payload=UMAAuthorizationPayload(
audience=client.client_id,
subject_token=token.auth_token, # user token
permissions=['otago/users#view']
)
)
User Information Retrieval
To retrieve user information, use the get_user_info_async method:
from pykeycloak_client.pykeycloak import PyKeycloak
pkc = PyKeycloak()
# add client ....
user_info = await pkc.get('otago_client').auth.get_user_info_async(
access_token=refresh.auth_token
)
Logout
To log out, use the logout_async method:
from pykeycloak_client.pykeycloak import PyKeycloak
pkc = PyKeycloak()
# add client ....
await pkc.get('otago_client').auth.logout_async(refresh.refresh_token)
Certificate Retrieval
To retrieve certificates, use the get_certs_async method:
from pykeycloak_client.pykeycloak import PyKeycloak
pkc = PyKeycloak()
# add client ....
certs = await pkc.get('otago_client').well_known.get_certs_async()
Other Methods
All services are available in protocols.py with their methods.
License
This project is licensed under the MIT License.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pykeycloak_client-0.8.2.tar.gz.
File metadata
- Download URL: pykeycloak_client-0.8.2.tar.gz
- Upload date:
- Size: 40.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
51684615d493c6e1340cbff2a0c43b7b7cbc32d3652f29290a584f5deebefc07
|
|
| MD5 |
7eef217ab45c25411f70e6c17ca2bcf2
|
|
| BLAKE2b-256 |
4b5829f2119fc169aa5360d560cd7f85b7891fa9e152d52b15f7484b5fee7254
|
File details
Details for the file pykeycloak_client-0.8.2-py3-none-any.whl.
File metadata
- Download URL: pykeycloak_client-0.8.2-py3-none-any.whl
- Upload date:
- Size: 56.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3cacc427bba71b93a44ecafb9f02dc5c7e64b59eaae7a9de34238d167108532a
|
|
| MD5 |
28df26af63c6e26a1866e03f2b39538f
|
|
| BLAKE2b-256 |
1071852b13e21b01291f052c36d73f38b06ea4bdf25abb55622b5850f64fba48
|