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 | Provenance | Actions |
|---|---|---|---|---|
| sysnet_auth-0.3.0.tar.gz | 43.6 kB | Without provenance | Details for sysnet_auth-0.3.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| sysnet_auth-0.3.0-py3-none-any.whl | Python 3 | none | any | Details for sysnet_auth-0.3.0-py3-none-any.whl |
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
|
Security for sysnet-auth 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.