sysnet-auth (import path: auth_lib)
Sdílená autentizační knihovna pro FastAPI mikroslužby, které ověřují identitu uživatelů přes Keycloak (OIDC / JWT). Součást SYSNET ekosystému.
Knihovna je záměrně úzká: neobsahuje business logiku, neukládá stav a nepřeposílá tokeny. Jediná zodpovědnost je validace JWT a vystavení objektu AuthenticatedUser dependency injection mechanismem FastAPI. Bez tokenu nebo při vypnuté autentizaci vrací anonymní identitu ANONYMOUS.
Proč existuje
V architektuře desítek mikroslužeb nechceme, aby každá z nich měla vlastní implementaci validace JWT. Rozkol v drobných detailech (kontrola audience, leeway, cachování JWKS) je snadný způsob, jak vyrobit bezpečnostní díru. auth_lib je jediné místo, kde se validuje identita uživatele — a všechny služby ji používají stejně.
Instalace
Balíček je publikovaný jako sysnet-auth, importuje se jako auth_lib
(běžný pattern: distribuční jméno ≠ import jméno).
pip install sysnet-auth
from auth_lib import get_current_user, require_role
Python
Vyžaduje Python ≥ 3.11, plně testováno proti 3.11 – 3.14.
Runtime závislosti
| Balíček | Role |
|---|---|
fastapi |
DI / exception handlery |
pydantic v2 |
modely, validace |
pydantic-settings |
konfigurace přes env |
pyjwt[crypto] |
dekódování + validace JWT |
httpx |
async HTTP klient pro JWKS endpoint |
sysnet-pyutils |
sdílené SYSNET modely (UserType, ErrorModel, Log) |
Volba JWT knihovny.
python-joseje od 2021 neudržovaný. Používáme PyJWT — aktivně udržovaný de facto standard pro JWT v Pythonu.
Konfigurace
Všechno přes environment proměnné s prefixem AUTH_. Pydantic-settings načte settings při prvním volání get_settings() a cachuje je na proces.
Povinná konfigurace při zapnuté autentizaci
AUTH_KEYCLOAK_URL, AUTH_REALM a AUTH_AUDIENCE jsou povinné jen pokud AUTH_ENABLED=True (nebo 1). Při výchozím AUTH_ENABLED=False není potřeba konfigurace Keycloaku a všichni uživatelé jsou ANONYMOUS. Při zapnuté autentizaci chybějící hodnoty způsobí validační chybu konfigurace, nikoli tichý přechod na anonymní identitu. To platí i pro offline režim AUTH_VERIFY_KEYCLOAK=False.
| Proměnná | Default | Popis |
|---|---|---|
AUTH_KEYCLOAK_URL |
"" |
Základní URL Keycloaku (https://kc.example.cz); povinná při AUTH_ENABLED=True |
AUTH_REALM |
"" |
Název Keycloak realm; povinná při AUTH_ENABLED=True |
AUTH_AUDIENCE |
"" |
Očekávaný aud claim (typicky client_id API); povinná při AUTH_ENABLED=True |
Volitelné proměnné
| Proměnná | Default | Popis |
|---|---|---|
AUTH_ENABLED |
False |
Zapíná/vypíná autentizaci: False = všichni ANONYMOUS, True = validace dodaného JWT |
AUTH_VERIFY_KEYCLOAK |
True |
False = offline vývojový režim bez načítání JWKS a ověření podpisu; NIKDY v produkci |
AUTH_ALGORITHMS |
["RS256"] |
Povolené podpisové algoritmy (CSV nebo JSON) |
AUTH_JWKS_CACHE_SECONDS |
300 |
TTL JWKS cache |
AUTH_JWKS_HTTP_TIMEOUT_SECONDS |
5.0 |
HTTP timeout pro fetch JWKS |
AUTH_LEEWAY_SECONDS |
0 |
Tolerance hodinového rozdílu (clock skew) |
AUTH_RESOURCE_CLIENT |
None |
Pokud nastaveno, mergne i resource_access.<klient>.roles |
AUTH_KID_MISS_REFRESH_COOLDOWN_SECONDS |
0 |
Opt-in: refresh při neznámém kid 1× za N sekund |
Odvozené hodnoty
- Issuer:
{AUTH_KEYCLOAK_URL}/realms/{AUTH_REALM} - JWKS URL:
{issuer}/protocol/openid-connect/certs
Použití ve FastAPI
Rychlý start — prostředí
Před spuštěním aplikace nastav proměnné prostředí pro svůj Keycloak. Bez AUTH_ENABLED=1 zůstává autentizace vypnutá, i když pošleš Bearer token.
export AUTH_ENABLED=1
export AUTH_KEYCLOAK_URL=https://kc.example.cz
export AUTH_REALM=myrealm
export AUTH_AUDIENCE=myapi
export AUTH_VERIFY_KEYCLOAK=True
Ověřený uživatel
from fastapi import Depends, FastAPI
from auth_lib import AuthenticatedUser, get_current_user, install_exception_handlers
app = FastAPI()
install_exception_handlers(app)
@app.get("/me")
async def me(user: AuthenticatedUser = Depends(get_current_user)) -> AuthenticatedUser:
return user
S platným Bearer tokenem a zapnutou autentizací vrací endpoint ověřeného uživatele. Samotné Depends(get_current_user) však přihlášení nevynucuje — bez tokenu vrací anonymní identitu.
Anonymní uživatel a vypnutá autentizace
Pokud token chybí (nebo je prázdný) NEBO AUTH_ENABLED=False, get_current_user vrací singleton ANONYMOUS: sub='anonymous', is_anonymous=True, roles=[]. Při vypnuté autentizaci se dodaný token ignoruje a JWT se nevaliduje.
from fastapi import Depends, FastAPI
from auth_lib import ANONYMOUS, ANONYMOUS_SUB, AuthenticatedUser, get_current_user
app = FastAPI()
assert ANONYMOUS.sub == ANONYMOUS_SUB == "anonymous"
assert ANONYMOUS.is_anonymous is True
@app.get("/resource")
async def resource(user: AuthenticatedUser = Depends(get_current_user)):
if user.is_anonymous:
return {"access": "public only", "sub": ANONYMOUS_SUB}
return {"access": "full", "sub": user.sub}
- Při
AUTH_ENABLED=Trueneplatný nebo expirovaný token stále vrací 401 — není nahrazen anonymní identitou. - Anonymní uživatel nemá role; role guard proto vrací 403.
- Endpointy vyžadující přihlášení musí explicitně odmítnout
user.is_anonymousnebo použít odpovídající role guard.
Hotový /whoami endpoint
Není potřeba psát vlastní endpoint pro zjištění identity. Knihovna exportuje volitelný auth_router:
from fastapi import FastAPI
from auth_lib import auth_router, install_exception_handlers
app = FastAPI()
install_exception_handlers(app)
app.include_router(auth_router, prefix='/auth', tags=['auth'])
# GET /auth/whoami → AuthenticatedUser nebo ANONYMOUS
GET /auth/whoami vrací JSON modelu AuthenticatedUser, včetně pole is_anonymous. Bez tokenu nebo při vypnuté autentizaci vrací ANONYMOUS (sub="anonymous", is_anonymous=true). Prefix i tagy můžeš upravit podle své aplikace.
Role guard
from auth_lib import require_role, require_any_role, require_all_roles
@app.delete("/users/{id}")
async def delete_user(
id: str,
user: AuthenticatedUser = Depends(require_role("admin")),
):
...
@app.get("/content")
async def list_content(
user: AuthenticatedUser = Depends(require_any_role(["editor", "viewer"])),
):
...
Konverze do SYSNET UserType
from auth_lib import get_current_user
@app.get("/user-profile")
async def profile(user = Depends(get_current_user)):
sysnet_user = user.to_user_type() # sysnet_pyutils.UserType
return sysnet_user
Mapping: sub → identifier, preferred_username → name, email → email, given_name → name_first, family_name → name_last, name → name_full.
Observability
Knihovna neví, co je Prometheus / OpenTelemetry / strukturovaný log. Místo toho exponuje pub-sub hooky, které si konzument zaregistruje:
from auth_lib import on_token_validated, on_token_rejected, on_jwks_refresh
@on_token_validated
def _ok(user):
metrics.incr("auth.ok", tags={"sub": user.sub})
@on_token_rejected
def _err(exc):
metrics.incr("auth.err", tags={"type": type(exc).__name__})
@on_jwks_refresh
def _jwks(n):
metrics.gauge("auth.jwks.keys", n)
Výjimka v hooku nikdy neshodí validaci (hooky jsou best-effort).
Rychlé zapnutí přes SYSNET logger
from auth_lib import install_sysnet_logging
install_sysnet_logging() # zapíše INFO/WARNING přes sysnet_pyutils.Log
Idempotentní — druhé volání už hooky znovu neregistruje.
Výjimky
| Výjimka | HTTP | Kdy |
|---|---|---|
InvalidTokenError |
401 | špatný podpis, expirace, iss, aud, neznámý kid, … |
MissingRoleError |
403 | chybí požadovaná role, včetně anonymního uživatele |
AuthConfigurationError |
500 | nedostupný JWKS, špatné URL, malformed response |
Všechny dědí z AuthError.
install_exception_handlers(app) registruje handler, který vrací sysnet_pyutils.ErrorModel:
{"code": 401, "message": "Token has expired"}
Jednotný formát chyb napříč SYSNET službami.
JWKS caching
- In-memory TTL cache (default 300 s).
- Lazy fetch při prvním volání vyžadujícím ověření podpisu (
AUTH_ENABLED=True, dodaný token,AUTH_VERIFY_KEYCLOAK=True). - Single-flight: souběh N requestů při cold/expired cache spustí právě jeden fetch.
- Fresh cache je autoritativní — kid miss =
InvalidTokenError. Brání DoS přes náhodné kidy. - Opt-in cooldown refresh (
AUTH_KID_MISS_REFRESH_COOLDOWN_SECONDS > 0): při kid miss ve fresh cache povolí 1 refresh za N sekund — responzivnější reakce na rotaci klíčů. - Lazy init
asyncio.Lock— kompatibilní s Pythonem 3.14 (neváže lock na event loop z doby importu). - Žádný background task, žádný disk.
Bezpečnostní poznámky
- Při
AUTH_ENABLED=TrueaAUTH_VERIFY_KEYCLOAK=Trueověřujeme podpis, issuer i audience. Audience může být řetězec nebo seznam obsahující očekávanéAUTH_AUDIENCE. - Pro produkční autentizaci nastav
AUTH_ENABLED=1a ponechAUTH_VERIFY_KEYCLOAK=True. Offline režim vypíná ověření podpisu a není bezpečný pro produkci. - Výchozí
AUTH_ENABLED=Falseznamená anonymní identitu pro všechny požadavky, nikoli ochranu endpointů. Přístup řiď explicitní kontrolou identity nebo rolí. - Výchozí
leeway=0. Zapnout jen při doloženém clock skew. - Pouze
RS256výchozí,noneani HS256 nepovolujeme bez dobrého důvodu. - Čteme pouze
Authorization: Bearer <token>(žádné cookies, žádnéX-*hlavičky). - Tokeny se nelogují. Diagnostika přes
sub/jti. - JWKS fetch má timeout → nedostupný Keycloak vrátí 500, ne čekání donekonečna.
Struktura projektu
auth_lib/
├── auth_lib/
│ ├── __init__.py # veřejné re-exporty (__all__)
│ ├── config.py # AuthSettings, AUTH_ENABLED, AUTH_VERIFY_KEYCLOAK
│ ├── exceptions.py # AuthError + to_error_model()
│ ├── models.py # AuthenticatedUser, is_anonymous, ANONYMOUS, ANONYMOUS_SUB
│ ├── jwks.py # async JWKS cache + cooldown
│ ├── dependencies.py # get_current_user, install_exception_handlers
│ ├── router.py # auth_router s GET /whoami
│ ├── roles.py # require_role / require_any_role / require_all_roles
│ ├── observability.py # hook registry + install_sysnet_logging()
│ └── py.typed # PEP 561 marker
├── tests/
├── pyproject.toml # sysnet-auth, Python 3.11-3.14
├── CHANGELOG.md
├── README.md
└── .gitignore
Testování
pip install -e ".[dev]"
pytest --cov=auth_lib --cov-report=term-missing
Testy nevolají reálný Keycloak — používají vlastní RSA keypair a JWKS cache předvyplněnou odpovídajícím JWK. 71 testů, 98 % pokrytí. Pokrývají také anonymní identitu, přepínač autentizace, /whoami, offline režim a audience jako seznam.
Architektonický princip
Tato knihovna je jediným místem, kde se řeší validace identity uživatele.
Všechny FastAPI mikroslužby ji používají jednotným způsobem. Pokud narazíš na potřebu obejít auth_lib (vlastní dekódování, custom validace), je to signál k úpravě auth_lib — ne k duplikaci logiky.
Verze 0.3.0 — novinky oproti 0.2.0
Anonymní identita a přepínač autentizace
ANONYMOUS singleton a pole is_anonymous sjednocují chování bez tokenu. AUTH_ENABLED má výchozí hodnotu False pro zpětně kompatibilní zapojení do služeb; JWT validaci aktivuje AUTH_ENABLED=1.
Hotový router
auth_router poskytuje GET /whoami pro vrácení aktuální identity bez vlastní implementace endpointu.
Offline režim (bez Keycloaku)
Nastav AUTH_ENABLED=1 a AUTH_VERIFY_KEYCLOAK=False pro dekódování tokenu bez JWKS a ověřování podpisu. Konfigurace URL, realm a audience zůstává povinná; kontroly issueru, audience a časových claimů zůstávají aktivní. Vhodné pouze pro lokální vývoj, NIKDY v produkci.
Podpora Keycloak audience listu
Keycloak standardně vrací aud jako seznam (["client-id", "account"]). Knihovna správně zpracuje jak řetězec, tak seznam; očekávané AUTH_AUDIENCE musí odpovídat řetězci nebo být jednou z položek seznamu.
Metadata
Release files for sysnet-auth 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sysnet_auth-0.3.0.tar.gz | 43.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sysnet_auth-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 76.2 kB
Release files / sysnet_auth-0.3.0.tar.gz
| Download URL | sysnet_auth-0.3.0.tar.gz |
|---|---|
| Size | 43.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
295e30e5c6f59c0b80f6d94f3d18aa90871e118386369cbde3a8719ce5b751c7
|
|
BLAKE2b-256 checksum How to use checksums |
b3464317dde810176ba9c41de42b6109192bf216bf1af05b5b4afdcd5b17c740
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / sysnet_auth-0.3.0-py3-none-any.whl
| Download URL | sysnet_auth-0.3.0-py3-none-any.whl |
|---|---|
| Size | 32.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d7d0c0fa94a023f7f9b92a5de9ca5dc36aa9f014983b0b3d1f54c47d02d36ed3
|
|
BLAKE2b-256 checksum How to use checksums |
b8b08d0c7cb9fe0aa78a78f10247d093c3a35ea739739fc53cd4aa6ecfa009c7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|