Retour au cours

backend / fastapi

CORS

Leçon 141 exercice

Explication

Ce que vous allez apprendre

  • Comprendre pourquoi le navigateur bloque par défaut une requête cross-origin
  • Configurer CORSMiddleware avec les bonnes origines autorisées
  • Comprendre le rôle de la requête preflight OPTIONS déclenchée automatiquement
  • Éviter la combinaison interdite allow_origins=["*"] avec allow_credentials=True
  • Autoriser dynamiquement un ensemble de sous-domaines avec allow_origin_regex

Dans quel contexte ?

Le frontend React hébergé sur https://monapp.com tente d'appeler l'API FastAPI hébergée sur https://api.monapp.com, mais la console du navigateur affiche "has been blocked by CORS policy". Aucune erreur n'apparaît côté serveur : la requête n'a même pas atteint l'API, car le navigateur bloque la réponse avant de la transmettre au code JavaScript, faute d'en-tête Access-Control-Allow-Origin correspondant. Ajouter CORSMiddleware avec allow_origins=["https://monapp.com"] sur l'API résout le problème.

Le problème, d'abord

Par défaut, les navigateurs appliquent une règle de sécurité stricte appelée "same-origin policy". Une page web chargée depuis un domaine ne peut PAS, par défaut, faire une requête vers une API hébergée sur un domaine différent.

Même si cette API appartient à la même équipe. C'est une protection contre des sites malveillants qui tenteraient d'utiliser silencieusement les identifiants d'un utilisateur sur un autre site.

CORS permet de lever cette restriction, mais de façon contrôlée. C'est un mécanisme où le SERVEUR indique explicitement, via des en-têtes HTTP, quelles origines il autorise.

Le navigateur, avant certaines requêtes "sensibles", envoie automatiquement une requête préliminaire appelée "preflight". Il bloque ou laisse passer la vraie requête selon la réponse reçue à cette préliminaire.

Une idée reçue fréquente chez les débutants mérite d'être corrigée ici. On pourrait penser qu'on peut contourner une erreur CORS depuis le code JavaScript du frontend.

C'est faux : la vérification est faite par le NAVIGATEUR, en se basant sur ce que répond le SERVEUR. La seule vraie solution est donc de configurer correctement le serveur, via CORSMiddleware, pour qu'il autorise explicitement les origines légitimes.

Configurationallow_credentials=True compatible ?
allow_origins=["https://monapp.com"]Oui
allow_origins=["*"]Non — interdit par la spécification CORS
allow_origin_regex=r"https://.*\.monapp.com"Oui

Piège de sécurité

Autoriser allow_origins=["*"] en même temps que allow_credentials=True est en réalité interdit par la spécification CORS elle-même, car cette combinaison ouvrirait la porte à n'importe quel site tiers pour utiliser les identifiants d'un utilisateur connecté. Le wildcard "*" ne devrait être utilisé QUE pour une API réellement publique et sans notion de session ou de cookies.

Commandes & code

CORS

python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://monapp.com", "https://admin.monapp.com"],
    allow_credentials=True,       # autorise l'envoi de cookies cross-origin
    allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
    allow_headers=["Authorization", "Content-Type"],
    expose_headers=["X-Request-ID"],  # headers custom lisibles par le JS du frontend
    max_age=3600,                       # durée de cache du préflight OPTIONS
)
python
# Origines dynamiques selon l'environnement — configuration centralisée
from app.core.config import settings

app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.cors_origins,  # ex. issu de la variable d'env CORS_ORIGINS
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
python
# app/core/config.py
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    cors_origins: list[str] = ["http://localhost:3000"]  # dev par défaut

    class Config:
        env_file = ".env"

settings = Settings()
bash
# .env.production
CORS_ORIGINS=["https://monapp.com","https://admin.monapp.com"]
python
# Piège fréquent : allow_origins=["*"] est INCOMPATIBLE avec allow_credentials=True
# (la spec CORS l'interdit explicitement pour des raisons de sécurité)

# MAUVAIS — ne fonctionnera pas avec des cookies/credentials
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,  # le navigateur rejettera cette combinaison
)

# BON — wildcard uniquement pour une API publique SANS credentials
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=False,
)
python
# Origin regex — pour autoriser dynamiquement des sous-domaines (previews Vercel par ex.)
app.add_middleware(
    CORSMiddleware,
    allow_origin_regex=r"https://.*\.monapp-preview\.com",
    allow_credentials=True,
)

Résumé

  • allow_origins doit lister explicitement les domaines autorisés, jamais "*" en production avec cookies.
  • allow_credentials=True et allow_origins=["*"] sont mutuellement incompatibles selon la spécification CORS.
  • allow_origin_regex est utile pour autoriser un ensemble dynamique de sous-domaines (previews, environnements éphémères).
  • Le middleware CORS gère automatiquement les requêtes preflight OPTIONS, sans code supplémentaire.

Exercices pratiques

1 disponible
1

Mission : le frontend bloqué par CORS et la config dangereuse

Objectif : Diagnostiquer une erreur CORS entre deux domaines distincts et corriger une configuration CORS incompatible avec les credentials.

Contexte

Le frontend React hébergé sur https://monapp.com appelle l'API FastAPI sur https://api.monapp.com et la console affiche "has been blocked by CORS policy". Un développeur, pour aller vite, a alors configuré CORSMiddleware avec allow_origins=["*"] et allow_credentials=True, mais les requêtes avec cookies échouent toujours.

Résoudre l’exercice →