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.

Bots never expose HTTP. The OIDC callback runs in a companion process — keycloak-sso-gateway (separate package / repo). This library is the bot-side adapter only (polling / outbound IdP access). Wire DTOs (SSOSession, login events, Redis prefixes) are duplicated in both packages and must stay in sync (schema_version).

 Telegram user           bot (aiogram-keycloak-sso)         callback service         IdP
──────────────          ─────────────────         ────────────────         ──────
 /login  ──────────────▶ FleetBotManager
                         state = bot:<bot_id>:signed
 opens URL ──────────────────────────────────────────────────────────────────▶ login
                                                   ◀── redirect ── code+state
                                                   code → tokens, store Redis
                                                   publish login event
                         consume_login_events ◀─── Redis pub/sub
 any update ───────────▶ SSOSessionMiddleware
                         (session from Redis; refresh ───────────────────────▶)
 flagged handler ──────▶ SSOAccessMiddleware + @sso_required

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.

The HTTP callback service is not included — run keycloak-sso-gateway as a separate process (python -m keycloak_sso_gateway / Docker Compose).

Quick start (bot process)

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",  # gateway public URL
    state_secret="long-random-string-keep-it-secret",     # same as on the gateway
    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)  — no inbound HTTP

Runnable example: examples/bot_alpha.py (env template: .env.example).

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 (gateway) 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"
)

Login chat hooks live on consume_login_events (Redis pub/sub from the gateway). The bot does not handle the OIDC HTTP callback.

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 on the bot, pass a SessionEnricher into FleetBotManager / SSOManager — it runs again on each OIDC token refresh. Initial enrichment at login belongs in the gateway (it writes the session).

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 gateway 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
keycloak-sso-gateway SSOGateway + HTTP callback — the only public HTTPS endpoint

Wire contracts (duplicated in both 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 gateway is down, new logins pause; bots keep serving live Redis sessions.

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.1.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.1.1
File Size Uploaded
aiogram_keycloak_sso-0.1.1.tar.gz 142.5 kB Details

Built distribution (wheel)

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

Total release size: 182.8 kB

Release files / aiogram_keycloak_sso-0.1.1.tar.gz

Download URL aiogram_keycloak_sso-0.1.1.tar.gz
Size 142.5 kB
Tags Source
SHA-256 checksum
How to use checksums
41111d42ac66da453a9a03647849793db29f5c26def9bd8dea1a5df3388c924d
BLAKE2b-256 checksum
How to use checksums
ff596e74c31492456df681251fc351c9574c9a546373014986f02f9dce567e42
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.1.1-py3-none-any.whl

Download URL aiogram_keycloak_sso-0.1.1-py3-none-any.whl
Size 40.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3add96c5e4407d3d1d32fa5e368104d06189f3348f6a89040eb6f3479a2bdf6e
BLAKE2b-256 checksum
How to use checksums
03622a00962c065733e82ad2dcd23b71b2a4aa219a90b253a9b9b0faf3b1f25f
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

0.2.0

2 release files

0.1.2

2 release files

This release

0.1.1 This release

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