Pourquoi Securiser une application FastAPI ?
Dans le monde numérique, la sécurité est un préoccupation majeure pour les développeurs. Un système non sécurisé peut être facilement exploité par des attaquants malveillants qui peuvent accéder à vos données sensibles, modifier ou même détruire votre application. Avec FastAPI, une de ces frameworks Python puissants et modernes, la sécurité est également une priorité.
Un cas d'usage concret : imaginez que vous développiez une API de gestion des utilisateurs pour une entreprise. Si cette API n'est pas sécurisée, un pirate informatique pourrait voler les informations des utilisateurs, ce qui pourrait entraîner des dommages financiers et réputationnels.
Prerequis
Connaissances Necessaires :
- Python (version recommandée : 3.7+)
- FastAPI (version recommandée : 0.68.1)
- Uvicorn ou Hypercorn pour l'exécution de l'application
- SQLAlchemy (pour les opérations de base de données)
Outils à Installer :
pip install fastapi uvicorn sqlalchemy databases[postgresql]
Concepts Fondamentaux
1. Authentication et Authorization
Authentication est le processus par lequel une application vérifie l'identité d'un utilisateur. Authorization, en revanche, détermine les actions que cet utilisateur peut effectuer dans l'application.
## Importations nécessaires pour FastAPI et OAuth2
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
2. Middleware
Les middleware sont des fonctions qui s'exécutent avant ou après l'traitement de la requête par le routeur FastAPI.
## Exemple de middleware pour vérifier les autorisations
from fastapi import Request, HTTPException
async def get_current_user(request: Request):
auth_header = request.headers.get("Authorization")
if not auth_header:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing Authorization Header")
# Vérifiez le token ici et renvoyez l'utilisateur
return {"user": "admin"}
3. Hashing des Mot de Passe
Les mots de passe doivent toujours être hashés pour stocker les informations de connexion en toute sécurité.
## Importation pour le hashing des mots de passe
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
Mise en Pratique : Projet Fil Rouge
Étape 1: Création de l'Application FastAPI
Créons un nouveau projet FastAPI.
mkdir fastapi-security-demo
cd fastapi-security-demo
uvicorn main:app --reload
Créez le fichier main.py :
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
app = FastAPI()
## Middleware pour vérifier les autorisations
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
response = await call_next(request)
return response
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
## Exemple de modèle utilisateur
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "johndoe@example.com",
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", # Hasse du mot de passe "secret"
}
}
## Fonction pour obtenir les informations de l'utilisateur
async def get_current_user(token: str = Depends(oauth2_scheme)):
user = fake_users_db.get(token)
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication credentials")
return {"user": user["username"]}
## Route pour obtenir le token d'authentification
@app.post("/token", response_model={"access_token": str})
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = fake_users_db.get(form_data.username)
if not user or not verify_password(form_data.password, user["hashed_password"]):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token = form_data.username # Dans un scénario réel, utiliser une bibliothèque pour générer des tokens sécurisés
return {"access_token": access_token}
## Route protégée
@app.get("/protected")
async def protected_route(current_user: dict = Depends(get_current_user)):
return {"message": f"Hello {current_user['user']}", "status_code": 200}
Étape 2: Exécution de l'Application
Exécutez votre application FastAPI :
uvicorn main:app --reload
Accédez à http://127.0.0.1:8000/docs pour voir les routes générées par Swagger UI.
Étape 3: Test de la Authentification
Testez la route /token pour obtenir un token d'authentification :
curl -X POST "http://127.0.0.1:8000/token" -d "username=johndoe&password=secret"
Vous devriez recevoir un token comme réponse.
Étape 4: Accès à la Route Protégée
Utilisez le token pour accéder à la route protégée :
curl -X GET "http://127.0.0.1:8000/protected" -H "Authorization: Bearer your_access_token"
Erreurs Frequentes et Debugging
1. Mauvaise Vérification du Token d'Authentification
Code Incorrect :
async def get_current_user(token: str = Depends(oauth2_scheme)):
user = fake_users_db.get(token)
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication credentials")
return {"user": user["username"]}
Code Correct :
async def get_current_user(token: str = Depends(oauth2_scheme)):
user = fake_users_db.get(token)
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication credentials")
return {"user": user["username"]}
2. Erreur de Vérification du Mot de Passe
Code Incorrect :
def verify_password(plain_password, hashed_password):
if plain_password == hashed_password:
return True
return False
Code Correct :
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
3. Erreur de Middleware
Code Incorrect :
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
response = await call_next(request)
return response
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def get_current_user(token: str = Depends(oauth2_scheme)):
user = fake_users_db.get(token)
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication credentials")
return {"user": user["username"]}
Code Correct :
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
response = await call_next(request)
return response
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def get_current_user(token: str = Depends(oauth2_scheme)):
user = fake_users_db.get(token)
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication credentials")
return {"user": user["username"]}
Pour Aller Plus loin
1. Authentification avec OAuth2 et JWT
Pour une authentification plus sécurisée, utiliser OAuth2 avec des tokens JWT.
Documentation FastAPI - OAuth2 and JWT
2. Base de Données Sécurisés
Utiliser SQLAlchemy pour les opérations de base de données et PostgreSQL pour la gestion sécurisée des données.
3. Gestion des Sessions Utilisateurs
Gérer les sessions utilisateurs avec FastAPI et OAuth2.
Documentation FastAPI - User Authentication with OAuth2 and JWT
Défi Pratique
Créez une application FastAPI qui utilise OAuth2 et JWT pour l'authentification. Implémentez des routes protégées pour accéder aux données sensibles.