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.
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.
| Proměnná | Default | Popis |
|---|---|---|
AUTH_KEYCLOAK_URL |
— | Základní URL Keycloaku (https://kc.example.cz) |
AUTH_REALM |
— | Název Keycloak realm |
AUTH_AUDIENCE |
— | Očekávaný aud claim (typicky client_id API) |
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
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
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 | uživatel je ověřen, ale chybí mu role |
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í.
- 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
- Ověřujeme vždy podpis, issuer i audience.
- 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(žá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
│ ├── exceptions.py # AuthError + to_error_model()
│ ├── models.py # AuthenticatedUser + to_user_type()
│ ├── jwks.py # async JWKS cache + cooldown
│ ├── dependencies.py # get_current_user, install_exception_handlers
│ ├── 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. 58 testů, 98 % pokrytí.
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.2.4 — novinky
Offline režim (bez Keycloaku)
Nastav AUTH_VERIFY_KEYCLOAK=False pro přeskočení ověřování podpisu. Vhodné pro lokální vývoj, nikdy ne v produkci.
Podpora Keycloak audience listu
Keycloak standardně vrací aud jako seznam (["client-id", "account"]). Knihovna to od 0.2.4 zpracuje správně.
Metadata
Release files for sysnet-auth 0.2.13
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.2.13.tar.gz | 39.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sysnet_auth-0.2.13-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 70.4 kB
Release files / sysnet_auth-0.2.13.tar.gz
| Download URL | sysnet_auth-0.2.13.tar.gz |
|---|---|
| Size | 39.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0e44fb8f2bd3f3919e28dd022b3c430daa3e973dd52cd147f0a4dd1ec572c3b0
|
|
BLAKE2b-256 checksum How to use checksums |
17abea60704b899a5d05c2ca822b9ab3a5bd0384941128ca92ba88cb51516d38
|
| 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.2.13-py3-none-any.whl
| Download URL | sysnet_auth-0.2.13-py3-none-any.whl |
|---|---|
| Size | 30.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
31982a844a016a027a6b65a90370c311316a02e403c6b7fccfaae0b2f22c6e5e
|
|
BLAKE2b-256 checksum How to use checksums |
6bea84fbd851a7e9d0fd43c27adb105c387f04f770fed0b771a71b63f0674de9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|