Authentification & Autorisation — arclith¶
Pipeline JWT Keycloak mutualisé FastAPI + FastMCP. Un seul cœur (auth_pipeline.py), deux wrappers minces.
Architecture¶
Composants clés¶
| Fichier | Rôle |
|---|---|
adapters/inbound/auth_pipeline.py |
Cœur du pipeline — unique source de vérité |
adapters/inbound/fastapi/auth.py |
Wrapper FastAPI : make_require_auth() |
adapters/inbound/fastmcp/auth.py |
Wrapper FastMCP : make_require_auth_tool() |
adapters/inbound/fastapi/dependencies.py |
Pipeline complet multitenant (FastAPI) |
adapters/inbound/fastmcp/dependencies.py |
Pipeline complet multitenant (FastMCP) |
adapters/inbound/jwt/decoder.py |
JWTDecoder — JWKS Keycloak avec cache |
adapters/inbound/license/validator.py |
RoleLicenseValidator — vérifie un realm role |
adapters/outbound/vault/tenant_adapter.py |
VaultTenantResolver — résout les coords tenant |
adapters/context.py |
ContextVar tenant par requête |
arclith.py |
Arclith.auth_dependency(transport) — factory depuis config |
Configuration¶
Depuis un projet généré par arclith-cli, la configuration Keycloak peut être
ajoutée sans toucher aux routers ni aux tools :
| Bash | |
|---|---|
Le CLI génère config/adapters/inbound/keycloak.yaml, chargé comme section
keycloak par Arclith("config"). audience valide les tokens reçus par
l'API ou les tools MCP. client_id sert uniquement au client public Swagger UI
OAuth2 PKCE ; il peut différer de l'audience si le realm sépare clients front,
Swagger et services machine-to-machine.
| YAML | |
|---|---|
La licence par rôle Keycloak se configure aussi sans changer les routers FastAPI ni les tools MCP :
| Bash | |
|---|---|
Le CLI génère config/adapters/inbound/license.yaml, chargé comme section
license par Arclith("config").
Si config.license existe, Arclith.auth_dependency() ajoute automatiquement
RoleLicenseValidator(config.license.role) au pipeline partagé FastAPI/MCP. Si
la section est absente, seul le JWT est vérifié. Un token absent ou invalide
reste une erreur 401, tandis qu'un token valide sans le rôle demandé retourne
403.
Les autres sections restent dans leurs fichiers de configuration dédiés ou dans
un config.yaml consolidé selon le mode de déploiement :
Multi-worker / Kubernetes : toujours
cache.backend: redisen production. Chaque worker avecmemorymaintient son propre cache — le JWKS sera re-fetché à chaque restart.
FastAPI — Cas d'usage¶
1. Protéger un router entier¶
Toutes les routes du router exigent un token valide.
2. Protéger une route individuelle¶
3. Injecter les claims dans un endpoint¶
| Python | |
|---|---|
4. Vérifier un rôle spécifique dans un endpoint¶
| Python | |
|---|---|
5. Pipeline complet — auth + licence + tenant (multitenant)¶
Le pipeline multitenant est câblé une seule fois dans le container, via un middleware global sur le router. Il n'est pas nécessaire de le répéter sur chaque route.
6. Router protégé sans tenant (auth seule)¶
| Python | |
|---|---|
FastMCP — Cas d'usage¶
1. Protéger un tool individuellement¶
| Python | |
|---|---|
2. Protéger tous les tools d'une classe¶
3. Injecter les claims dans un tool¶
| Python | |
|---|---|
4. Pipeline complet multitenant (FastMCP)¶
5. Auth seule vs auth + tenant — choisir le bon¶
| Besoin | FastAPI | FastMCP |
|---|---|---|
| Auth seule | arclith.auth_dependency() |
arclith.auth_dependency(transport="mcp") |
| Auth + licence | arclith.auth_dependency() (licence configurée) |
Idem |
| Auth + licence + tenant | make_inject_tenant_uri(config, ...) |
make_inject_tenant_uri(config, ...) |
La licence est automatiquement incluse dans
auth_dependency()siconfig.licenseest défini. Elle est également incluse dansmake_inject_tenant_urisilicense_validatorest passé.
Depends vs décorateur Python¶
Pourquoi Depends et non @decorator¶
Avantages de Depends :
- Swagger UI — avec
config.keycloak, le bouton "Authorize" expose le schéma OAuth2 PKCE Keycloak. - Typage complet —
Annotated[dict, Depends(...)]est visible par mypy/pyright. - Composabilité — plusieurs
Dependspeuvent se chaîner (inject_tenantdépend lui-même du JWT). - Testabilité —
app.dependency_overrides[require_auth] = lambda: {"sub": "test-user"}pour les tests. - Unification FastAPI / FastMCP — même mécanique de dépendances dans les deux frameworks.
Surcharge avec dependency_overrides (tests)¶
| Python | |
|---|---|
Swagger UI — tester avec un Bearer Token¶
Quand config.keycloak est présent, Arclith.fastapi() :
1. Injecte le schéma OAuth2 PKCE dans l'OpenAPI spec (/openapi.json)
2. Pré-configure swagger_ui_init_oauth avec clientId et PKCE
3. Remplace le schéma HTTPBearer généré par FastAPI par le seul schéma
keycloak pour éviter un champ bearer vide et confus dans Swagger UI
Le bouton "Authorize" apparaît dans Swagger UI (/docs) avec un seul schéma
keycloak (OAuth2, authorizationCode).
Option A — OAuth2 PKCE (recommandé en dev et démonstration) :
1. Cliquer "Authorize"
2. Sélectionner le schéma keycloak (OAuth2, authorizationCode)
3. Scopes : openid profile
4. Keycloak redirige → login → token automatiquement injecté
Option B — bearer machine-to-machine hors Swagger UI :
Swagger UI reste volontairement en OAuth2 PKCE. Les clients techniques, tests
automatisés, MCP HTTP/SSE et appels Postman/curl utilisent le même pipeline
serveur avec Authorization: Bearer <access_token>.
Granularité par rôle — patterns avancés¶
Vérification inline dans un endpoint¶
| Python | |
|---|---|
Dependency réutilisable par rôle¶
Rôles disponibles (exemple Rekipe)¶
| Rôle | Usage |
|---|---|
rekipe:licensed |
Accès de base (vérifié automatiquement si config.license.role est défini) |
rekipe:premium |
Fonctionnalités avancées |
rekipe:admin |
Administration (purge, management) |
rekipe:trial |
Accès limité (durée, quota) |
Limitations et points de vigilance¶
Transport stdio — incompatible avec l'auth¶
Le transport MCP stdio ne supporte pas les headers HTTP. Toute authentification JWT est impossible.
Ne pas utiliser stdio en production. Voir ADR-007 dans docs/decisions.md.
| Text Only | |
|---|---|
Traitements longs et expiration de token¶
Les JWT Keycloak ont une durée de vie courte (5–15 minutes par défaut). Pour les traitements longs passant par un event bus (Kafka, RabbitMQ) :
⚠️ Ne jamais transmettre le JWT brut dans la payload d'un message.
Raisons :
- Le token expirera avant ou pendant le traitement
- Le consumer n'a pas de mécanisme de refresh (pas de refresh_token en M2M)
- Le token peut être révoqué entre l'émission et la consommation
Pattern correct — extraire le tenant_id avant l'envoi :
| Python | |
|---|---|
Multi-worker / Kubernetes — cache Redis obligatoire¶
En mode single-worker (dev), cache.backend: memory suffit.
En production (plusieurs replicas), chaque worker a son propre cache mémoire :
- Le JWKS est re-fetché par chaque worker à son démarrage
- Les coords tenant ne sont pas partagées entre workers
| YAML | |
|---|---|
Single-tenant sans Keycloak¶
Si config.keycloak est absent :
- Arclith.auth_dependency() lève RuntimeError
- make_inject_tenant_uri avec multitenant: false est un no-op (aucune vérification)
- Les services internes (appelés uniquement par des couches supérieures déjà authentifiées) peuvent omettre config.keycloak
| YAML | |
|---|---|
Récapitulatif des patterns¶
| Scénario | FastAPI | FastMCP |
|---|---|---|
| Auth seule, router entier | APIRouter(dependencies=[Depends(require_auth)]) |
Depends sur chaque tool |
| Auth seule, route unique | add_api_route(..., dependencies=[Depends(require_auth)]) |
_auth: Annotated[dict, Depends(require_auth_mcp)] |
| Claims dans handler | claims: Annotated[dict, Depends(require_auth)] |
Idem avec ctx: Context en plus |
| Auth + licence | auth_dependency() avec config.license défini |
Idem |
| Auth + licence + tenant | Depends(make_inject_tenant_uri(...)) sur le router |
Depends(make_inject_tenant_uri(...)) dans le tool |
| Vérif rôle custom | require_role("rekipe:admin") inline ou factory |
Idem, vérif inline après Depends |
| Tests unitaires | app.dependency_overrides[fn] = lambda: {...} |
Mock inject_tenant_uri |
| Service interne sans auth | Omettre config.keycloak, multitenant: false |
Idem |
Références¶
arclith/adapters/inbound/auth_pipeline.py— cœur du pipelinearclith/adapters/inbound/fastapi/auth.py— wrapper FastAPIarclith/adapters/inbound/fastmcp/auth.py— wrapper FastMCPdocs/multitenant.md— détail du pipeline multitenantdocs/decisions.md— ADR-007 (stdio), ADR-008 (pipeline mutualisé)SKILLS.md→ SK-F09 — recette pas-à-pas