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.0

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.0
File Size Uploaded
aiogram_keycloak_sso-0.2.0.tar.gz 147.3 kB Details

Built distribution (wheel)

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

Total release size: 191.0 kB

Release files / aiogram_keycloak_sso-0.2.0.tar.gz

Download URL aiogram_keycloak_sso-0.2.0.tar.gz
Size 147.3 kB
Tags Source
SHA-256 checksum
How to use checksums
bc4479ce415ff0aad7df18492301f53146bd08cf3577c690d1f2011ebb6b9d29
BLAKE2b-256 checksum
How to use checksums
bdbb397da4fd2c51336785f5e4c35a4b0c38fa7aa336c12ac902d3aeaf02f9cd
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.0-py3-none-any.whl

Download URL aiogram_keycloak_sso-0.2.0-py3-none-any.whl
Size 43.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
213b12d9851ceb751248dc9deeb5bd675c65d20eaab5417287e9d28bb3a5e292
BLAKE2b-256 checksum
How to use checksums
787cfa400ded1564f1a3b7fd84dfda875609f095f6715da9eb7c87ff5e30192e
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

0.2.1

2 release files

This release

0.2.0 This release

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