Sdilena autentizacni knihovna pro FastAPI mikrosluzby s Keycloack (OIDC/JWT).
Project description
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.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sysnet_auth-0.2.0.tar.gz.
File metadata
- Download URL: sysnet_auth-0.2.0.tar.gz
- Upload date:
- Size: 24.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
27ac53c545032910f26f8d0ea8c41f761ec9ca32966e91685a1254d1f6410914
|
|
| MD5 |
e9f76f0494527e7ba5ed763a7285feb0
|
|
| BLAKE2b-256 |
1ef7bfd1f5418faf887ed18b20aa0d041724d9d96ee4441f8f8eef224a8b01e7
|
File details
Details for the file sysnet_auth-0.2.0-py3-none-any.whl.
File metadata
- Download URL: sysnet_auth-0.2.0-py3-none-any.whl
- Upload date:
- Size: 16.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7b610e8818792fd2005fbf2e63fd617013a78331c1013709dca137675addebc6
|
|
| MD5 |
09c1c0d1698bfbb941888c94f41e31b8
|
|
| BLAKE2b-256 |
057d58f069f51784164719b73f0827446682b9d15d5405210ffcf3afcc76f2a1
|