Skip to main content

Sdilena autentizacni knihovna pro FastAPI mikrosluzby s Keycloack (OIDC/JWT).

Project description

 1|# sysnet-auth (import path: `auth_lib`)
 2|
 3|[![GitHub](https://img.shields.io/badge/GitHub-SYSNET--CZ%2Fauth--lib-181717?style=flat&logo=github)](https://github.com/SYSNET-CZ/auth-lib)
 4|[![PyPI - Version](https://img.shields.io/pypi/v/sysnet-auth)](https://pypi.org/project/sysnet-auth/)
 5|[![Python Version](https://img.shields.io/pypi/pyversions/sysnet-auth)](https://pypi.org/project/sysnet-auth/)
 6|[![FastAPI](https://img.shields.io/badge/FastAPI-0.115%2B-009688?style=flat&logo=fastapi)](https://fastapi.tiangolo.com)
 7|[![Tests](https://img.shields.io/badge/tests-58%20passed-brightgreen)](https://github.com/SYSNET-CZ/auth-lib)
 8|[![Coverage](https://img.shields.io/badge/coverage-98%25-brightgreen)](https://github.com/SYSNET-CZ/auth-lib)
 9|[![License](https://img.shields.io/badge/license-AGPL--3-blue)](https://github.com/SYSNET-CZ/auth-lib/blob/main/LICENSE)
10|
11|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.
12|
13|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.
14|
15|---
16|
17|## Proč existuje
18|
19|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ě.
20|
21|---
22|
23|## Instalace
24|
25|Balíček je publikovaný jako **`sysnet-auth`**, importuje se jako **`auth_lib`**
26|(běžný pattern: distribuční jméno ≠ import jméno).
27|
28|```bash
29|pip install sysnet-auth
30|```
31|
32|```python
33|from auth_lib import get_current_user, require_role
34|```
35|
36|### Python
37|
38|Vyžaduje **Python ≥ 3.11**, plně testováno proti **3.11 – 3.14**.
39|
40|### Runtime závislosti
41|
42|| Balíček             | Role                                           |
43||---------------------|------------------------------------------------|
44|| `fastapi`           | DI / exception handlery                        |
45|| `pydantic` v2       | modely, validace                               |
46|| `pydantic-settings` | konfigurace přes env                           |
47|| `pyjwt[crypto]`     | dekódování + validace JWT                      |
48|| `httpx`             | async HTTP klient pro JWKS endpoint            |
49|| `sysnet-pyutils`    | sdílené SYSNET modely (`UserType`, `ErrorModel`, `Log`) |
50|
51|> **Volba JWT knihovny.** `python-jose` je od 2021 neudržovaný. Používáme **PyJWT** — aktivně udržovaný de facto standard pro JWT v Pythonu.
52|
53|---
54|
55|## Konfigurace
56|
57|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.
58|
59|| Proměnná                            | Default      | Popis |
60||-------------------------------------|--------------|-------|
61|| `AUTH_KEYCLOAK_URL`                 | —            | Základní URL Keycloaku (`https://kc.example.cz`) |
62|| `AUTH_REALM`                        | —            | Název Keycloak realm |
63|| `AUTH_AUDIENCE`                     | —            | Očekávaný `aud` claim (typicky `client_id` API) |
64|| `AUTH_ALGORITHMS`                   | `["RS256"]`  | Povolené podpisové algoritmy (CSV nebo JSON) |
65|| `AUTH_JWKS_CACHE_SECONDS`           | `300`        | TTL JWKS cache |
66|| `AUTH_JWKS_HTTP_TIMEOUT_SECONDS`    | `5.0`        | HTTP timeout pro fetch JWKS |
67|| `AUTH_LEEWAY_SECONDS`               | `0`          | Tolerance hodinového rozdílu (clock skew) |
68|| `AUTH_RESOURCE_CLIENT`              | `None`       | Pokud nastaveno, mergne i `resource_access.<klient>.roles` |
69|| `AUTH_KID_MISS_REFRESH_COOLDOWN_SECONDS` | `0`     | Opt-in: refresh při neznámém `kid` 1× za N sekund |
70|
71|### Odvozené hodnoty
72|
73|- **Issuer:** `{AUTH_KEYCLOAK_URL}/realms/{AUTH_REALM}`
74|- **JWKS URL:** `{issuer}/protocol/openid-connect/certs`
75|
76|---
77|
78|## Použití ve FastAPI
79|
80|### Ověřený uživatel
81|
82|```python
83|from fastapi import Depends, FastAPI
84|from auth_lib import AuthenticatedUser, get_current_user, install_exception_handlers
85|
86|app = FastAPI()
87|install_exception_handlers(app)
88|
89|@app.get("/me")
90|async def me(user: AuthenticatedUser = Depends(get_current_user)) -> AuthenticatedUser:
91|    return user
92|```
93|
94|### Role guard
95|
96|```python
97|from auth_lib import require_role, require_any_role, require_all_roles
98|
99|@app.delete("/users/{id}")

100|async def delete_user( 101| id: str, 102| user: AuthenticatedUser = Depends(require_role("admin")), 103|): 104| ... 105| 106|@app.get("/content") 107|async def list_content( 108| user: AuthenticatedUser = Depends(require_any_role(["editor", "viewer"])), 109|): 110| ... 111| 112| 113|### Konverze do SYSNET `UserType` 114| 115|python 116|from auth_lib import get_current_user 117| 118|@app.get("/user-profile") 119|async def profile(user = Depends(get_current_user)): 120| sysnet_user = user.to_user_type() # sysnet_pyutils.UserType 121| return sysnet_user 122| 123| 124|Mapping: `sub → identifier`, `preferred_username → name`, `email → email`, `given_name → name_first`, `family_name → name_last`, `name → name_full`. 125| 126|--- 127| 128|## Observability 129| 130|Knihovna neví, co je Prometheus / OpenTelemetry / strukturovaný log. Místo toho exponuje **pub-sub hooky**, které si konzument zaregistruje: 131| 132|python 133|from auth_lib import on_token_validated, on_token_rejected, on_jwks_refresh 134| 135|@on_token_validated 136|def _ok(user): 137| metrics.incr("auth.ok", tags={"sub": user.sub}) 138| 139|@on_token_rejected 140|def _err(exc): 141| metrics.incr("auth.err", tags={"type": type(exc).name}) 142| 143|@on_jwks_refresh 144|def _jwks(n): 145| metrics.gauge("auth.jwks.keys", n) 146| 147| 148|Výjimka v hooku **nikdy neshodí validaci** (hooky jsou best-effort). 149| 150|### Rychlé zapnutí přes SYSNET logger 151| 152|python 153|from auth_lib import install_sysnet_logging 154|install_sysnet_logging() # zapíše INFO/WARNING přes sysnet_pyutils.Log 155| 156| 157|Idempotentní — druhé volání už hooky znovu neregistruje. 158| 159|--- 160| 161|## Výjimky 162| 163|| Výjimka | HTTP | Kdy | 164||--------------------------|------|---------------------------------------------------------| 165|| `InvalidTokenError` | 401 | špatný podpis, expirace, `iss`, `aud`, neznámý `kid`, … | 166|| `MissingRoleError` | 403 | uživatel je ověřen, ale chybí mu role | 167|| `AuthConfigurationError` | 500 | nedostupný JWKS, špatné URL, malformed response | 168| 169|Všechny dědí z `AuthError`. 170| 171|`install_exception_handlers(app)` registruje handler, který vrací **`sysnet_pyutils.ErrorModel`**: 172| 173|json 174|{"code": 401, "message": "Token has expired"} 175| 176| 177|Jednotný formát chyb napříč SYSNET službami. 178| 179|--- 180| 181|## JWKS caching 182| 183|- In-memory TTL cache (default 300 s). 184|- Lazy fetch při prvním volání. 185|- **Single-flight:** souběh N requestů při cold/expired cache spustí právě jeden fetch. 186|- **Fresh cache je autoritativní** — kid miss = `InvalidTokenError`. Brání DoS přes náhodné kidy. 187|- **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íčů. 188|- Lazy init `asyncio.Lock` — kompatibilní s Pythonem 3.14 (neváže lock na event loop z doby importu). 189|- Žádný background task, žádný disk. 190| 191|--- 192| 193|## Bezpečnostní poznámky 194| 195|- Ověřujeme vždy **podpis, issuer i audience**. 196|- Výchozí `leeway=0`. Zapnout jen při doloženém clock skew. 197|- Pouze `RS256` výchozí, `none` ani HS256 nepovolujeme bez dobrého důvodu. 198|- Čteme **pouze `Authorization: Bearer`** (žádné cookies, žádné `X-*` hlavičky). 199|- Tokeny se nelogují. Diagnostika přes `sub` / `jti`. 200|- JWKS fetch má timeout → nedostupný Keycloak vrátí 500, ne čekání donekonečna. 201| 202|--- 203| 204|## Struktura projektu 205| 206| 207|auth_lib/ 208|├── auth_lib/ 209|│ ├── init.py # veřejné re-exporty (all) 210|│ ├── config.py # AuthSettings 211|│ ├── exceptions.py # AuthError + to_error_model() 212|│ ├── models.py # AuthenticatedUser + to_user_type() 213|│ ├── jwks.py # async JWKS cache + cooldown 214|│ ├── dependencies.py # get_current_user, install_exception_handlers 215|│ ├── roles.py # require_role / require_any_role / require_all_roles 216|│ ├── observability.py # hook registry + install_sysnet_logging() 217|│ └── py.typed # PEP 561 marker 218|├── tests/ 219|├── pyproject.toml # sysnet-auth, Python 3.11-3.14 220|├── CHANGELOG.md 221|├── README.md 222|└── .gitignore 223| 224| 225|--- 226| 227|## Testování 228| 229|bash 230|pip install -e ".[dev]" 231|pytest --cov=auth_lib --cov-report=term-missing 232|``` 233| 234|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í. 235| 236|--- 237| 238|## Architektonický princip 239| 240|> Tato knihovna je jediným místem, kde se řeší validace identity uživatele. 241| 242|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. 243|

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ě.

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

sysnet_auth-0.2.9.tar.gz (39.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sysnet_auth-0.2.9-py3-none-any.whl (30.5 kB view details)

Uploaded Python 3

File details

Details for the file sysnet_auth-0.2.9.tar.gz.

File metadata

  • Download URL: sysnet_auth-0.2.9.tar.gz
  • Upload date:
  • Size: 39.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for sysnet_auth-0.2.9.tar.gz
Algorithm Hash digest
SHA256 95b8d9b9cb12e2b2b1d057d124d52c30ab338349277e814641e7305276016a65
MD5 bb923a3294224248ed95b0264b94ec8a
BLAKE2b-256 0cb7f7bc4ba3af5f7a4026e6d295cd2a3e384f37f16d1043c0268422d95a5b16

See more details on using hashes here.

File details

Details for the file sysnet_auth-0.2.9-py3-none-any.whl.

File metadata

  • Download URL: sysnet_auth-0.2.9-py3-none-any.whl
  • Upload date:
  • Size: 30.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for sysnet_auth-0.2.9-py3-none-any.whl
Algorithm Hash digest
SHA256 8c6be4000f519e2b90d00169157905f26b43bf709601edeb618d87d23da2650a
MD5 2ad0496112c93ff02d60c2743b1a5e8c
BLAKE2b-256 ea51630193bad5bb43ac96c55a1243a12b00cb250216ddbd2069985c497b81fc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page