Authentification HTTP Basic¶
🌐 Traduction par IA et humains
Cette traduction a été réalisée par une IA guidée par des humains. 🤝
Elle peut contenir des erreurs d'interprétation du sens original, ou paraître peu naturelle, etc. 🤖
Vous pouvez améliorer cette traduction en nous aidant à mieux guider le LLM d'IA.
Pour les cas les plus simples, vous pouvez utiliser l'authentification HTTP Basic.
Avec l'authentification HTTP Basic, l'application attend un en-tête contenant un nom d'utilisateur et un mot de passe.
Si elle ne le reçoit pas, elle renvoie une erreur HTTP 401 « Unauthorized ».
Et elle renvoie un en-tête WWW-Authenticate avec la valeur Basic, et un paramètre optionnel realm.
Cela indique au navigateur d'afficher l'invite intégrée pour saisir un nom d'utilisateur et un mot de passe.
Ensuite, lorsque vous saisissez ce nom d'utilisateur et ce mot de passe, le navigateur les envoie automatiquement dans l'en-tête.
Authentification HTTP Basic simple¶
- Importer
HTTPBasicetHTTPBasicCredentials. - Créer un « schéma de sécurité » en utilisant
HTTPBasic. - Utiliser ce
securityavec une dépendance dans votre chemin d'accès. - Cela renvoie un objet de type
HTTPBasicCredentials:- Il contient le
usernameet lepasswordenvoyés.
- Il contient le
from typing import Annotated
from fastapi import Depends, FastAPI
from fastapi.security import HTTPBasic, HTTPBasicCredentials
app = FastAPI()
security = HTTPBasic()
@app.get("/users/me")
def read_current_user(credentials: Annotated[HTTPBasicCredentials, Depends(security)]):
return {"username": credentials.username, "password": credentials.password}
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from fastapi import Depends, FastAPI
from fastapi.security import HTTPBasic, HTTPBasicCredentials
app = FastAPI()
security = HTTPBasic()
@app.get("/users/me")
def read_current_user(credentials: HTTPBasicCredentials = Depends(security)):
return {"username": credentials.username, "password": credentials.password}
Lorsque vous essayez d'ouvrir l'URL pour la première fois (ou cliquez sur le bouton « Execute » dans les documents) le navigateur vous demandera votre nom d'utilisateur et votre mot de passe :

Vérifier le nom d'utilisateur¶
Voici un exemple plus complet.
Utilisez une dépendance pour vérifier si le nom d'utilisateur et le mot de passe sont corrects.
Pour cela, utilisez le module standard Python secrets pour vérifier le nom d'utilisateur et le mot de passe.
secrets.compare_digest() doit recevoir des bytes ou une str ne contenant que des caractères ASCII (ceux de l'anglais), ce qui signifie qu'elle ne fonctionnerait pas avec des caractères comme á, comme dans Sebastián.
Pour gérer cela, nous convertissons d'abord username et password en bytes en les encodant en UTF-8.
Nous pouvons ensuite utiliser secrets.compare_digest() pour vérifier que credentials.username est « stanleyjobson » et que credentials.password est « swordfish ».
import secrets
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
app = FastAPI()
security = HTTPBasic()
def get_current_username(
credentials: Annotated[HTTPBasicCredentials, Depends(security)],
):
current_username_bytes = credentials.username.encode("utf8")
correct_username_bytes = b"stanleyjobson"
is_correct_username = secrets.compare_digest(
current_username_bytes, correct_username_bytes
)
current_password_bytes = credentials.password.encode("utf8")
correct_password_bytes = b"swordfish"
is_correct_password = secrets.compare_digest(
current_password_bytes, correct_password_bytes
)
if not (is_correct_username and is_correct_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Basic"},
)
return credentials.username
@app.get("/users/me")
def read_current_user(username: Annotated[str, Depends(get_current_username)]):
return {"username": username}
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
import secrets
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
app = FastAPI()
security = HTTPBasic()
def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
current_username_bytes = credentials.username.encode("utf8")
correct_username_bytes = b"stanleyjobson"
is_correct_username = secrets.compare_digest(
current_username_bytes, correct_username_bytes
)
current_password_bytes = credentials.password.encode("utf8")
correct_password_bytes = b"swordfish"
is_correct_password = secrets.compare_digest(
current_password_bytes, correct_password_bytes
)
if not (is_correct_username and is_correct_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Basic"},
)
return credentials.username
@app.get("/users/me")
def read_current_user(username: str = Depends(get_current_username)):
return {"username": username}
Cela serait équivalent à :
if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"):
# Renvoyer une erreur
...
Mais en utilisant secrets.compare_digest(), cela sera sécurisé contre un type d'attaques appelé « attaques par chronométrage ».
Attaques par chronométrage¶
Mais qu'est-ce qu'une « attaque par chronométrage » ?
Imaginons que des attaquants essaient de deviner le nom d'utilisateur et le mot de passe.
Ils envoient alors une requête avec un nom d'utilisateur johndoe et un mot de passe love123.
Le code Python de votre application serait alors équivalent à quelque chose comme :
if "johndoe" == "stanleyjobson" and "love123" == "swordfish":
...
Mais au moment où Python compare le premier j de johndoe au premier s de stanleyjobson, il retournera False, car il sait déjà que ces deux chaînes ne sont pas identiques, en se disant qu'« il n'est pas nécessaire de gaspiller plus de calcul pour comparer le reste des lettres ». Et votre application dira « Nom d'utilisateur ou mot de passe incorrect ».
Mais ensuite, les attaquants essaient avec le nom d'utilisateur stanleyjobsox et le mot de passe love123.
Et le code de votre application fait quelque chose comme :
if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
...
Python devra comparer tout stanleyjobso dans stanleyjobsox et stanleyjobson avant de réaliser que les deux chaînes ne sont pas identiques. Cela prendra donc quelques microsecondes supplémentaires pour répondre « Nom d'utilisateur ou mot de passe incorrect ».
Le temps de réponse aide les attaquants¶
À ce stade, en remarquant que le serveur a mis quelques microsecondes de plus à envoyer la réponse « Nom d'utilisateur ou mot de passe incorrect », les attaquants sauront qu'ils ont trouvé quelque chose de juste : certaines des premières lettres étaient correctes.
Ils peuvent alors réessayer en sachant que c'est probablement quelque chose de plus proche de stanleyjobsox que de johndoe.
Une attaque « professionnelle »¶
Bien sûr, les attaquants n'essaieraient pas tout cela à la main ; ils écriraient un programme pour le faire, avec éventuellement des milliers ou des millions de tests par seconde. Ils obtiendraient une lettre correcte supplémentaire à la fois.
Ce faisant, en quelques minutes ou heures, les attaquants devineraient le nom d'utilisateur et le mot de passe corrects, avec « l'aide » de notre application, simplement en se basant sur le temps de réponse.
Corrigez-le avec secrets.compare_digest()¶
Mais dans notre code nous utilisons justement secrets.compare_digest().
En bref, il faudra le même temps pour comparer stanleyjobsox à stanleyjobson que pour comparer johndoe à stanleyjobson. Il en va de même pour le mot de passe.
Ainsi, en utilisant secrets.compare_digest() dans le code de votre application, votre application sera protégée contre toute cette gamme d'attaques de sécurité.
Renvoyer l'erreur¶
Après avoir détecté que les identifiants sont incorrects, renvoyez une HTTPException avec un code d'état 401 (le même que lorsque aucun identifiant n'est fourni) et ajoutez l'en-tête WWW-Authenticate pour que le navigateur affiche à nouveau l'invite de connexion :
import secrets
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
app = FastAPI()
security = HTTPBasic()
def get_current_username(
credentials: Annotated[HTTPBasicCredentials, Depends(security)],
):
current_username_bytes = credentials.username.encode("utf8")
correct_username_bytes = b"stanleyjobson"
is_correct_username = secrets.compare_digest(
current_username_bytes, correct_username_bytes
)
current_password_bytes = credentials.password.encode("utf8")
correct_password_bytes = b"swordfish"
is_correct_password = secrets.compare_digest(
current_password_bytes, correct_password_bytes
)
if not (is_correct_username and is_correct_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Basic"},
)
return credentials.username
@app.get("/users/me")
def read_current_user(username: Annotated[str, Depends(get_current_username)]):
return {"username": username}
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
import secrets
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
app = FastAPI()
security = HTTPBasic()
def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
current_username_bytes = credentials.username.encode("utf8")
correct_username_bytes = b"stanleyjobson"
is_correct_username = secrets.compare_digest(
current_username_bytes, correct_username_bytes
)
current_password_bytes = credentials.password.encode("utf8")
correct_password_bytes = b"swordfish"
is_correct_password = secrets.compare_digest(
current_password_bytes, correct_password_bytes
)
if not (is_correct_username and is_correct_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Basic"},
)
return credentials.username
@app.get("/users/me")
def read_current_user(username: str = Depends(get_current_username)):
return {"username": username}