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.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.1.0
File Size Uploaded
aiogram_keycloak_sso-0.1.0.tar.gz 142.3 kB Details

Built distribution (wheel)

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

Total release size: 182.4 kB

Release files / aiogram_keycloak_sso-0.1.0.tar.gz

Download URL aiogram_keycloak_sso-0.1.0.tar.gz
Size 142.3 kB
Tags Source
SHA-256 checksum
How to use checksums
90fe2ff373093fa38edcd0a7b1f8c9e92f66c51a2b9df1b4c6bd980ae08b31cd
BLAKE2b-256 checksum
How to use checksums
f1930df0bc73e950f2c8b62b3a7c1ff3af2edd2e107cb8bb608d129fef1270e6
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.0-py3-none-any.whl

Download URL aiogram_keycloak_sso-0.1.0-py3-none-any.whl
Size 40.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
76fcad7e21dacfefe71030aa5273138f72bece9e81abe172d306b004629a908c
BLAKE2b-256 checksum
How to use checksums
712dcf9ea7e5a1e7a1acd0f8d55da88c9dcd3758e0af8090c404833ec6aaca04
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

0.1.1

2 release files

This release

0.1.0 This release

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