CORS est un mécanisme du navigateur où un serveur déclare les autres origines autorisées à l’appeler, assouplissant sciemment la politique de même origine. Deux recadrages dissipent l’essentiel de la confusion : c’est le navigateur qui l’applique (pas le serveur — c’est pourquoi curl fonctionne), et c’est le serveur qui le corrige (pas votre frontend — c’est pourquoi aucune quantité de JavaScript n’y change rien). Tout le reste, ce sont des headers.
Points clés
| Question | Réponse |
|---|---|
| Le fondement | La politique de même origine (same-origin policy) : par défaut, les scripts ne peuvent pas lire les réponses d’autres origines |
| Une origine | schéma + hôte + port — les trois doivent correspondre |
| CORS | Des headers serveur qui autorisent des origines précises ; le navigateur applique |
| Requête préliminaire (preflight) | Un OPTIONS “puis-je ?” avant les requêtes non simples |
| La vérité éternelle | Les erreurs CORS se corrigent sur le serveur, point final |
Tout le protocole, sur le fil
# Preflight : le navigateur demande avant un PUT avec un header d'authentification
OPTIONS /classes/Product HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: x-parse-session-token
# Le bon pour accord du serveur
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com ← cette origine peut lire
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: x-parse-session-token
Access-Control-Max-Age: 7200 ← mettez cette réponse en cache ; pas de preflight répété
# Alors, et seulement alors, la vraie requête part.
Ce que cela donne depuis le code applicatif — et la vérité de plateforme que montrent les onglets : CORS est une affaire de navigateur, que les apps natives ne rencontrent jamais :
// Browser JavaScript — Back4app JS SDK
// A cross-origin call that just works: the platform answers the
// preflight and sends the CORS headers, so the browser lets it through
Parse.initialize('APP_ID', 'JS_KEY');
Parse.serverURL = 'https://parseapi.back4app.com'; // different origin than your site
const products = await new Parse.Query('Product').find();
// No proxy hacks, no "disable CORS" — the server side is configured correctly. // Flutter / Dart — Back4app Flutter SDK
// Native apps have no same-origin policy — CORS is a browser concern.
// Flutter Web, however, DOES enforce it: same browser rules apply there.
final query = QueryBuilder<ParseObject>(ParseObject('Product'));
final response = await query.query();
// Works identically on mobile and web because the server sends CORS headers. // iOS / Swift — Back4app Swift SDK
// No browser, no same-origin policy: CORS never applies to native iOS.
// The same backend serves browsers (with CORS headers) and apps alike.
let query = Product.query()
query.find { result in
if case .success(let products) = result { render(products) }
} // Android / Kotlin — Back4app Android SDK
// No browser, no same-origin policy: CORS never applies to native Android.
// The same backend serves browsers (with CORS headers) and apps alike.
val query = ParseQuery.getQuery<ParseObject>("Product")
query.findInBackground { products, e ->
if (e == null) render(products)
} Le flux, tranché
Deux détails portent l’essentiel des sessions de débogage : une requête simple (GET/HEAD/POST avec des headers de la safelist et des content types basiques) échappe au preflight — c’est pourquoi ajouter un header Authorization “casse” soudain un endpoint qui fonctionnait ; et les requêtes avec identifiants resserrent tout — les cookies ne circulent qu’avec Access-Control-Allow-Credentials: true plus une origine exacte, jamais le wildcard, selon la spécification.
Comment corriger les erreurs CORS courantes (erreur → cause → correctif)
| La console affiche | Ce qui s’est réellement passé | Correctif (toujours côté serveur) |
|---|---|---|
| No ‘Access-Control-Allow-Origin’ header | Le serveur n’a jamais autorisé votre origine | Ajoutez votre origine à la liste autorisée |
| Origin not allowed by Access-Control-Allow-Origin | La liste existe ; vous n’y figurez pas | Ajoutez le schéma+hôte+port exact |
| Response to preflight… doesn’t pass | OPTIONS non géré ou headers incomplets | Répondez à OPTIONS avec méthodes/headers |
| Wildcard ’*’ cannot be used with credentials | Cookies + * — combinaison interdite | Listez les origines explicitement |
| Request header not allowed | Header personnalisé absent de la liste autorisée | Ajoutez-le à Access-Control-Allow-Headers |
Et l’anti-correctif qui mérite d’être nommé : désactiver via un flag du navigateur ou passer par des proxies de dev permissifs rend l’erreur invisible sur votre machine uniquement — le déploiement reste cassé pour les utilisateurs. Le vrai correctif est une configuration serveur qui se compte en minutes.
Cas d’usage courants
- SPA + API sur des origines différentes — app.example.com qui appelle api.example.com : le cas quotidien pour lequel CORS existe.
- Développement local — localhost:3000 face à un vrai backend ; autorisez l’origine de dev, ne désactivez pas le bouclier.
- API publiques — origine wildcard, sans identifiants : correct et sûr pour des données réellement publiques.
- Plateformes multi-frontend — plusieurs apps, un backend, une liste explicite d’origines par environnement.
- Backends BaaS — la plateforme répond aux preflights et envoie les headers ; votre travail se réduit à déclarer les origines autorisées.
Wildcard vs. origines explicites : matrice de décision
| Configuration | Pertinent quand… | Jamais quand… |
|---|---|---|
Wildcard * | API publique, sans identifiants | Des cookies ou des sessions utilisateur existent |
| Liste explicite d’origines | API privées, apps avec identifiants | — (c’est la réponse par défaut) |
| Refléter l’origine de la requête | Presque jamais | Combiné avec des identifiants — la brèche classique |
| Listes par environnement | Hygiène dev/staging/prod | La prod hérite des entrées localhost du dev |
Un recadrage sécurité referme la matrice : CORS n’est pas du contrôle d’accès — les apps natives et les serveurs l’ignorent complètement, il protège donc les utilisateurs du navigateur contre les sites malveillants, pas votre API contre ses appelants. L’authentification et les permissions au niveau de la couche de données font ce travail ; CORS décide seulement quels scripts de quels sites peuvent lire les réponses.
Limites et trade-offs
- Il ne régit que les navigateurs. Tout ce qui n’est pas un navigateur passe outre — ne confondez jamais une liste d’origines avec une autorisation.
- Les preflights coûtent un aller-retour sur les requêtes non simples ; la mise en cache via
Access-Control-Max-Ageest le correctif bon marché et oublié. - Une mauvaise configuration échoue en mode fermé, et bruyamment — bien pour la sécurité, brutal pour le débogage sans le tableau erreur→correctif ci-dessus.
- Les listes d’origines sont un état d’environnement. Des origines de staging se glissent dans les configurations de production ; auditez la liste comme n’importe quel identifiant.
- CORS ≠ CSRF ≠ CSP. Des sigles voisins, des défenses distinctes — les cookies ont toujours besoin d’une protection SameSite/token, et les pages d’une politique de contenu, CORS ne gérant que la lecture cross-origin.
CORS sur Back4app
Back4app est une plateforme open-source de Backend as a Service (BaaS) qui combine une base de données gérée, des API REST et GraphQL générées automatiquement, l’authentification, le stockage de fichiers et des fonctions serverless avec Cloud Code. La corvée CORS est réglée d’office : les API de la plateforme répondent aux preflights et envoient les bons headers, si bien que les apps navigateur appellent le backend depuis les origines autorisées sans bricolage de proxy — l’onglet JavaScript ci-dessus est toute l’expérience — tandis que les onglets Flutter, Swift et Kotlin démontrent une vérité plus discrète : les clients natifs ne rencontrent jamais la cérémonie. La vraie sécurité reste là où elle doit être : sessions, ACL et permissions au niveau de la classe appliquées côté serveur à chaque requête, quelle que soit l’origine.
Questions fréquentes
Qu'est-ce que CORS, en termes simples ?
Le système de permissions du navigateur pour les appels d'API entre sites. Par défaut, les scripts d'une origine ne peuvent pas lire les réponses d'une autre — c'est la politique de même origine (same-origin policy). CORS est la façon dont un serveur donne son accord : des headers de réponse qui déclarent les origines, méthodes et headers qu'il accepte. Le navigateur applique ; le serveur déclare ; votre code frontend n'est que le messager.
Qu'est-ce qu'une origine, exactement ?
Le triplet schéma, hôte et port. https://app.example.com et https://api.example.com sont des origines différentes (l'hôte diffère) ; tout comme les versions http et https d'un même site (le schéma diffère), et :3000 vs :8080 en développement (le port diffère). Chaque décision CORS compare ces trois parties — rien d'autre dans l'URL ne compte.
Qu'est-ce qui provoque l'erreur CORS classique ?
Le navigateur a appelé une autre origine, et la réponse ne contenait pas de header Access-Control-Allow-Origin correspondant à la vôtre — le navigateur a donc empêché votre script de la lire. La requête a souvent bien atteint le serveur ; le blocage a lieu côté client, à la lecture. C'est pourquoi le correctif relève toujours de la configuration serveur, jamais du code frontend.
Qu'est-ce qu'une requête préliminaire (preflight) ?
La vérification de permission que le navigateur effectue à l'avance pour les requêtes non simples : avant d'envoyer un PUT, un DELETE ou quoi que ce soit avec des headers personnalisés comme un token d'autorisation, il envoie une requête OPTIONS qui demande "puis-je ?". Les headers du serveur répondent quelles méthodes, quels headers et quelles origines sont autorisés ; alors seulement la vraie requête part. Les preflights peuvent être mis en cache via Access-Control-Max-Age.
Pourquoi ma requête fonctionne-t-elle avec curl mais échoue-t-elle dans le navigateur ?
Parce que CORS est une contrainte appliquée par le navigateur, pas un rejet du serveur. Des outils comme curl et les apps mobiles natives n'ont pas de politique de même origine, et lisent donc la réponse sans broncher. Le navigateur applique la politique pour le compte de son utilisateur — la différence que vous constatez est le point d'application, et c'est aussi la preuve que CORS n'est pas du contrôle d'accès.
Access-Control-Allow-Origin: * est-il sûr ?
Pour des API réellement publiques et sans identifiants, oui. Le wildcard est interdit en mode avec identifiants — les navigateurs refusent par conception d'y associer des cookies — et refléter des origines arbitraires tout en autorisant les identifiants est la mauvaise configuration classique qui transforme CORS de bouclier en brèche. Les API privées listent les origines explicitement.
Puis-je simplement désactiver CORS pour corriger l'erreur ?
Seulement au sens où retirer un détecteur de fumée éteint un incendie. Les flags du navigateur et les proxies permissifs masquent le symptôme sur votre machine tout en livrant la panne à chaque utilisateur. Le bon correctif prend quelques minutes : configurez le serveur (ou le dashboard de la plateforme) pour autoriser les origines censées l'appeler.
Quelle est la différence entre CORS, CSRF et CSP ?
Trois rôles différents. CORS régit les origines autorisées à lire les réponses d'un serveur. CSRF est une attaque — profiter des cookies d'un utilisateur pour forger des requêtes — contrée par des tokens et des cookies SameSite, pas par CORS. CSP est la politique propre à une page qui restreint ce qu'elle peut charger et exécuter, l'outil anti-XSS. Des sigles voisins, des mécanismes orthogonaux.