Skip to content

Configurer l'authentification

nene2-python prend en charge l'authentification par Bearer Token et par clé API, toutes deux implémentées sous forme de middleware et activées via des variables d'environnement.

Bearer Token

Activation

Ajoutez dans votre fichier .env :

dotenv
BEARER_TOKEN_ENABLED=true
BEARER_TOKENS=["token1","token2","token3"]

Comportement

  • Chaque requête doit inclure Authorization: Bearer <token>
  • Les tokens sont comparés avec secrets.compare_digest (protection contre les attaques temporelles)
  • Les tokens invalides retournent 401 Unauthorized (RFC 9457 Problem Details)

Exemple

bash
curl -H "Authorization: Bearer token1" http://localhost:8080/notes

Clé API

Activation

dotenv
API_KEY_ENABLED=true
API_KEYS=["key1","key2"]

Comportement

  • Chaque requête doit inclure X-Api-Key: <key>
  • Les clés invalides retournent 401 Unauthorized

Exemple

bash
curl -H "X-Api-Key: key1" http://localhost:8080/notes

Utiliser les deux simultanément (condition ET)

Quand les deux middlewares sont ajoutés via add_middleware, les requêtes doivent passer les deux vérifications (condition ET). Un Bearer token seul ou une clé API seule resulte en 401.

Authentification exclusive (Bearer Token OU clé API)

Si vous souhaitez accepter soit un Bearer token, soit une clé API — selon ce qui est présent — les middlewares intégrés ne peuvent pas exprimer cela. Implémentez un middleware personnalisé qui réutilise les vérificateurs existants :

python
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
from starlette.responses import Response

from nene2.auth import LocalTokenVerifier
from nene2.auth.exceptions import TokenVerificationException
from nene2.http.problem_details import problem_details_response

class EitherOrAuthMiddleware(BaseHTTPMiddleware):
    def __init__(self, app, *, bearer_tokens: list[str], api_keys: list[str],
                 exclude_paths: list[str] | None = None) -> None:
        super().__init__(app)
        self._bearer = LocalTokenVerifier(bearer_tokens)
        self._api_key = LocalTokenVerifier(api_keys)
        self._exclude_paths = set(exclude_paths or [])

    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        if request.url.path in self._exclude_paths:
            return await call_next(request)

        auth = request.headers.get("Authorization", "")
        if auth.startswith("Bearer "):
            try:
                if self._bearer.verify(auth[len("Bearer "):]):
                    return await call_next(request)
            except TokenVerificationException:
                pass

        api_key = request.headers.get("X-Api-Key", "")
        if api_key:
            try:
                if self._api_key.verify(api_key):
                    return await call_next(request)
            except TokenVerificationException:
                pass

        return problem_details_response(
            "unauthorized", "Unauthorized", 401,
            "A valid Bearer token or X-Api-Key header is required.",
        )

Désactiver l'auth dans les tests

python
from nene2.config import AppSettings
from fastapi.testclient import TestClient
from example.app import create_app

client = TestClient(create_app(AppSettings(bearer_token_enabled=False)))

TokenVerifier personnalisé (p. ex. JWT)

Implémentez TokenVerifierProtocol et levez TokenVerificationException en cas d'échec.

python
from nene2.auth import TokenVerificationException, TokenVerifierProtocol
import jwt

class JwtTokenVerifier:
    def __init__(self, secret: str) -> None:
        self._secret = secret

    def verify(self, token: str) -> bool:
        try:
            jwt.decode(token, self._secret, algorithms=["HS256"])
            return True
        except jwt.InvalidTokenError as exc:
            raise TokenVerificationException(str(exc)) from exc

Passez votre vérificateur directement à BearerTokenMiddleware.

Charger les tokens depuis les variables d'environnement

Utilisez LocalTokenVerifier.from_env() pour éviter d'écrire le code de découpage et nettoyage à chaque fois :

dotenv
# .env
BEARER_TOKENS=token-a,token-b,token-c
python
from nene2.auth import BearerTokenMiddleware, LocalTokenVerifier

app.add_middleware(
    BearerTokenMiddleware,
    verifier=LocalTokenVerifier.from_env("BEARER_TOKENS"),
)

Une variable non définie ou vide produit une allowlist vide — toutes les requêtes sont refusées.

Exclure des chemins de l'authentification

Utilisez exclude_paths pour contourner l'auth pour les health checks et la doc de l'API :

python
app.add_middleware(
    BearerTokenMiddleware,
    verifier=LocalTokenVerifier.from_env("BEARER_TOKENS"),
    exclude_paths=["/docs", "/openapi.json", "/redoc", "/health"],
)

ApiKeyAuthMiddleware prend en charge le même paramètre.

Serveur MCP — échec rapide sur token manquant

Quand un serveur MCP appelle une API protégée par Bearer via HttpxMcpClient, validez le token au démarrage plutôt que de découvrir un token manquant lors de l'appel :

python
import os
from nene2.mcp.http_client import HttpxMcpClient

token = os.getenv("MCP_BEARER_TOKEN")
if not token:
    raise RuntimeError("MCP_BEARER_TOKEN is not set — cannot call the authenticated API.")
client = HttpxMcpClient(token)

TokenIssuer personnalisé (p. ex. JWT)

Implémentez TokenIssuerProtocol pour émettre des tokens (p. ex. pour un endpoint de login).

python
from nene2.auth import TokenIssuerProtocol
import jwt

class JwtTokenIssuer:
    def __init__(self, secret: str) -> None:
        self._secret = secret

    def issue(self, claims: dict[str, object]) -> str:
        return jwt.encode(claims, self._secret, algorithm="HS256")

Publié sous la licence MIT.