Skip to main content

aiogram-keycloak-sso

PyPI Python License: MIT

SSO (OpenID Connect / Keycloak) authentication middleware for aiogram 3: outer/inner middlewares, handler flags, DI through the data dict, and a swappable storage protocol.

This library is the bot-side adapter (login URL, session middleware, consume_login_events). Completing the OIDC browser callback is pluggable: any backend that writes the session and publishes a LoginEvent on the shared event bus works. Wire DTOs (SSOSession, login events, Redis prefixes) must stay in sync with that backend (schema_version).

SSO login flow

Callback backends (pick one)

Backend Role
keycloak-sso-gateway (GitHub) Optional ready-made process: code exchange, Redis sessions, RedisEventBus.publish
Your HTTP service Any stack that calls the same storage + bus contract (or embeds SSOManager.handle_callback)
CallbackHttpListener (this package) Opt-in minimal aiohttp GET /sso/callback; off by default until mount() / start()

Recommended for fleets: keep the callback in a separate process (gateway or your own). For a single bot / lab setup, attach an inline listener:

from aiogram_keycloak_sso import CallbackHttpListener, FleetBotManager, MemoryEventBus

bus = MemoryEventBus()  # or RedisEventBus
listener = CallbackHttpListener(event_bus=bus)
manager.add_listener(listener)          # bind only — no server yet
listener.mount(existing_aiohttp_app)    # or: await listener.start(port=8080)

How it maps onto aiogram's design

aiogram concept aiogram-keycloak-sso counterpart
outer middleware SSOSessionMiddleware — injects sso_session, renews at the IdP when TTL elapses
inner middleware + handler flags SSOAccessMiddleware + @sso_required
DI via data dict sso_session: SSOSession | None, sso_manager: SSOManager
FSM BaseStorage protocol BaseSessionStorage → MemorySessionStorage, RedisSessionStorage

Install

pip install aiogram-keycloak-sso
pip install "aiogram-keycloak-sso[redis]"   # Redis sessions + event bus

Import package name (underscore): aiogram_keycloak_sso.

Quick start (bot process + external callback)

from aiogram import Bot, Dispatcher, Router
from aiogram.filters import Command
from redis.asyncio import Redis

from aiogram_keycloak_sso import (
    AccessPolicy,
    FleetBotManager,
    SSOConfig,
    SSOSession,
    SSOSessionMiddleware,
    SSOAccessMiddleware,
    sso_required,
    login_notifier,
    login_failed_notifier,
    login_denied_notifier,
    consume_login_events,
)
from aiogram_keycloak_sso.events_redis import RedisEventBus
from aiogram_keycloak_sso.storage.redis import RedisSessionStorage

POLICY = AccessPolicy(groups=["/bots/alpha"])

redis = Redis.from_url("redis://localhost:6379/0")
config = SSOConfig(
    issuer="https://keycloak.example.com/realms/main",
    client_id="my-bot",
    client_secret="...",
    redirect_uri="https://sso.example.com/sso/callback",  # callback public URL
    state_secret="long-random-string-keep-it-secret",     # same as on the callback backend
    refresh_leeway=30,
)
manager = FleetBotManager("alpha", config, RedisSessionStorage(redis))

bot = Bot(token="...")
dp = Dispatcher()
router = Router()
dp.update.outer_middleware(SSOSessionMiddleware(manager, policy=POLICY))
router.message.middleware(SSOAccessMiddleware())
dp.include_router(router)

@router.message(Command("me"))
@sso_required
async def me(message, sso_session: SSOSession):
    await message.answer(f"{sso_session.username} / roles: {sso_session.realm_roles}")

@router.message(Command("admin"))
@sso_required(realm_roles=["admin"])
async def admin(message, sso_session: SSOSession): ...

await consume_login_events(
    RedisEventBus(redis),
    "alpha",
    on_login=login_notifier(bot),
    on_login_failed=login_failed_notifier(bot),
    policy=POLICY,
    on_denied=login_denied_notifier(bot),
)
# then: await dp.start_polling(bot)

Runnable example: examples/bot_alpha.py. Required process env vars are listed in .env.example (example only — the package does not load .env).

After a successful login

Hook Where Purpose
on_login / login_notifier via consume_login_events Telegram chat Success message, continue button (result.state.next)
on_denied / login_denied_notifier Telegram chat IdP ok, but bot policy rejected — "no access to this bot"
on_login_failed / login_failed_notifier Telegram chat Failure reaction
post_login_redirect (SSOConfig) Browser (callback backend) Deep link back to t.me/<bot>
from aiogram_keycloak_sso import login_notifier, login_failed_notifier, login_denied_notifier

await consume_login_events(
    bus, "alpha",
    on_login=login_notifier(bot),                # "✅ Signed in as {display_name}"
    on_login_failed=login_failed_notifier(bot),  # "❌ Sign-in was not completed..."
    policy=POLICY,
    on_denied=login_denied_notifier(bot),        # "❌ ... no access to this bot"
)

Chat hooks live on consume_login_events (event bus). The callback backend — gateway, custom service, or CallbackHttpListener — writes the session and publishes the event. Same-process setups can also pass on_login / on_login_failed into SSOManager / FleetBotManager directly.

Localization

Built-in strings (en / ru) are localizable; every component accepts locale:

SSOAccessMiddleware()                    # locale="auto" → Telegram language_code
SSOAccessMiddleware(locale="ru-RU")      # pin one language
login_notifier(bot)                      # auto: IdP locale claim, then Telegram

Add a language with register_texts("de", SSOTexts(...)).

Continue where the user left off (next)

SSOAccessMiddleware captures the interrupted action into signed state. After login it returns as result.state.next for a "continue" button.

Session lifecycle & TTL

  • TTL defaults to the access token's expires_in; session_ttl caps it.
  • Outer middleware refreshes via the refresh grant when TTL elapses (minus refresh_leeway). Concurrent updates for one user refresh once.
  • Rejected refresh → session dropped → protected handlers show a login button.
  • manager.logout(user_id) revokes the refresh token (RFC 7009) and deletes the session.

Optional claims enricher

Prefer putting org attributes in Keycloak / FreeIPA claims. If you need an extra directory lookup, pass a SessionEnricher into FleetBotManager / SSOManager — it runs on login (handle_callback) and again on each OIDC token refresh.

Security notes

  • state is HMAC-SHA256-signed (telegram user id, timestamp, nonce).
  • OIDC ID-token nonce is optional (require_id_token_nonce=False by default); enable it only if your IdP echoes nonce into the ID token.
  • ID tokens validated against realm JWKS (iss, aud, exp).

Keycloak setup

  1. Confidential client, Standard flow enabled.
  2. Add the callback URL to Valid redirect URIs (one URI for the fleet).
  3. Role mappers with Add to ID token enabled (realm_access.roles / resource_access.*.roles). Verify with Client scopes → Evaluate.

Multi-bot fleets

Package Role
aiogram-keycloak-sso (this repo) Middleware, flags, FleetBotManager, consume_login_events, optional CallbackHttpListener
keycloak-sso-gateway Optional ready-made callback server + Redis publish

Wire contracts (duplicated across bot + callback packages; keep in sync via schema_version):

Contract Default
Redis session keys keycloak_sso:session:{bot_id}:{telegram_user_id}
Event channels keycloak_sso:events:{bot_id}
State wire form bot:{bot_id}:{signed_state} (HMAC short keys u/c/n/t/x in each app's StateManager)

Isolation is derived from bot_id on both sides (storage namespace, event channel, derived state secret). If the callback backend is down, new logins pause; bots keep serving live sessions from storage.

Access control (AccessPolicy)

When per-bot Keycloak clients are not an option, authorize on the consumer:

from aiogram_keycloak_sso import AccessPolicy

POLICY = AccessPolicy(
    groups=["/bots/alpha"],
    claims={"department": ["R&D", "IT"]},
)

dp.update.outer_middleware(SSOSessionMiddleware(manager, policy=POLICY))
await consume_login_events(
    bus, "alpha",
    on_login=login_notifier(bot),
    policy=POLICY,
    on_denied=login_denied_notifier(bot),
)

Compose with | / & (RND | INVITED). Same object works with @sso_required(POLICY).

Custom storage

Implement BaseSessionStorage (get / set / delete). Redis backend: aiogram_keycloak_sso.storage.redis (requires [redis] extra).

Development

git clone https://github.com/ggb0yyyyyyy/sso-aiogram-midleware.git
cd sso-aiogram-midleware
pip install -e ".[dev,redis]"
pytest

License

MIT — see LICENSE.

Metadata

Release files for aiogram-keycloak-sso 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aiogram-keycloak-sso 0.2.1
File Size Uploaded
aiogram_keycloak_sso-0.2.1.tar.gz 147.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aiogram-keycloak-sso 0.2.1
File Interpreter ABI Platform
aiogram_keycloak_sso-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 190.9 kB

Release files / aiogram_keycloak_sso-0.2.1.tar.gz

Download URL aiogram_keycloak_sso-0.2.1.tar.gz
Size 147.4 kB
Tags Source
SHA-256 checksum
How to use checksums
b27aebb94ea08f41d537f2756079cf171fec5a2e2a8aa86741343c6f88986fbc
BLAKE2b-256 checksum
How to use checksums
a7f2cb7797ab46a1f5064b585e3b551bd2e94b68f7c5605407888886a62c7f6b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Release files / aiogram_keycloak_sso-0.2.1-py3-none-any.whl

Download URL aiogram_keycloak_sso-0.2.1-py3-none-any.whl
Size 43.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3c13c6bce88f7d26e78ea2dcb092991668a8c830bb47c117347f2b41bb21cb6d
BLAKE2b-256 checksum
How to use checksums
51d9c0757797a668252b40148ee2da69a06a0e00de6fc7a474563c2f47530c22
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

0.2.0

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page