CORS : pourquoi le navigateur bloque une API | POLPROG Aller au contenu

CORS : qu'est-ce que c'est et pourquoi le navigateur bloque une API

CORS n'est pas un mécanisme qui bloque simplement les connexions à une API. C'est un protocole du navigateur construit sur la same-origin policy qui décide si du JavaScript provenant d'un origin peut lire la réponse d'un autre origin. Dans certains cas, la requête HTTP est bien envoyée et seul l'accès du script à la réponse est bloqué. Dans d'autres cas, le navigateur envoie d'abord un preflight OPTIONS et utilise son résultat pour décider si la vraie requête peut être envoyée.

Publié Rédigé par Temps de lecture 10 min de lecture

CORS n'est pas un mécanisme qui bloque simplement les connexions à une API. C'est un protocole du navigateur construit sur la same-origin policy qui décide si du JavaScript provenant d'un origin peut lire la réponse d'un autre origin. Dans certains cas, la requête HTTP est bien envoyée et seul l'accès du script à la réponse est bloqué. Dans d'autres cas, le navigateur envoie d'abord un preflight OPTIONS et utilise son résultat pour décider si la vraie requête peut être envoyée.

Sur cette page
  1. 1CORS commence par la same-origin policy
  2. 2Ce qu'est exactement un origin
  3. 3Ce que signifie réellement navigateur bloque l'API
  4. 4Requêtes simples : quand le preflight est évité
  5. 5Preflight OPTIONS : ce que vérifie le navigateur
  6. 6Les principaux headers CORS
  7. 7`Access-Control-Allow-Origin` : wildcard ou origin explicite
  8. 8Cookies et credentials : source fréquente d'erreurs
  9. 9Pourquoi `mode: "no-cors"` ne corrige généralement rien
  10. 10Pourquoi curl, Postman ou un backend peuvent fonctionner
  11. 11CORS ne remplace ni CSRF ni l'autorisation
  12. 12Erreurs fréquentes et diagnostic
  13. 13Configurations CORS dangereuses
  14. 14Performance : coût du preflight et `Access-Control-Max-Age`
  15. 15Configuration pratique et checklist de déploiement

CORS commence par la same-origin policy

La same-origin policy est un mécanisme de sécurité fondamental du navigateur. Elle limite la capacité d'un document ou d'un script à lire les données d'un autre origin. Elle empêche notamment un site malveillant de lire les données d'un service où l'utilisateur est déjà connecté. [3]

CORS, Cross-Origin Resource Sharing, est un mécanisme HTTP qui permet au serveur d'assouplir cette règle et de déclarer explicitement quels origins peuvent lire une réponse. Le Fetch Standard le définit comme un protocole opt-in. [1][2]

Ce qu'est exactement un origin

PageURL cibleRelationPourquoi
https://app.example.comhttps://app.example.com/apiMême originMême schéma, hôte et port
https://app.example.comhttps://api.example.comOrigin différentHôte différent
https://app.example.comhttp://app.example.comOrigin différentSchéma différent
https://app.example.comhttps://app.example.com:8443Origin différentPort différent
https://app.example.comhttps://app.example.com/v2Même originSeul le chemin change

Un origin est défini par le schéma, l'hôte et le port. Deux URL sont same-origin uniquement si les trois correspondent. Le chemin ne compte pas. [3][4]

`https://app.example.com` et `https://api.example.com` sont donc des origins différents. HTTP et HTTPS diffèrent aussi, tout comme deux ports distincts. [4]

Same-origin et same-site ne sont pas équivalents. C'est important pour les cookies, car `SameSite` utilise la notion de site alors que CORS utilise l'origin. [13]

Ce que signifie réellement navigateur bloque l'API

Dire que le navigateur bloque la requête est souvent trop simplificateur. Pour une requête cross-origin simple, il peut l'envoyer, recevoir une réponse HTTP valide puis refuser de l'exposer à JavaScript si le contrôle CORS échoue. [1][6]

Pour les requêtes avec preflight, le comportement est différent. Le navigateur envoie d'abord `OPTIONS`. Si le preflight échoue, la vraie requête n'est pas envoyée. [2][5][6]

Cette distinction est importante pour les opérations qui modifient des données. Une erreur CORS ne signifie pas automatiquement que le backend n'a reçu aucune requête.

Requêtes simples : quand le preflight est évité

MéthodeHeaders d'exempleRequête simpleEffet
GETAcceptOuiPas de preflight
POSTContent-Type: text/plainOuiPas de preflight
POSTContent-Type: application/jsonNonJSON déclenche un preflight
PUTContent-Type: application/jsonNonLa méthode déclenche un preflight
GETAuthorization: Bearer ...NonLe header déclenche un preflight

Une requête n'est simple que si elle respecte la safelist CORS. Les méthodes autorisées sont `GET`, `HEAD` et `POST`. Les headers ajoutés manuellement doivent être safelisted, et `Content-Type` ne peut utiliser que `application/x-www-form-urlencoded`, `multipart/form-data` ou `text/plain`. [1]

Un `POST` avec `Content-Type: application/json` n'est généralement pas simple et déclenche un preflight. Même chose pour `PUT`, `DELETE` ou un header personnalisé comme `X-Request-ID`. [1][11]

Preflight OPTIONS : ce que vérifie le navigateur

Le preflight est une requête `OPTIONS` automatique. Le navigateur envoie `Origin`, `Access-Control-Request-Method` et si nécessaire `Access-Control-Request-Headers`. Le serveur répond avec les origins, méthodes et headers autorisés. [2][5]

Pour envoyer `DELETE` avec `Authorization`, le navigateur peut d'abord demander si cet origin a le droit d'utiliser cette méthode et ce header. Une réponse positive est nécessaire avant la vraie requête. [5]

Le preflight ajoute un aller-retour réseau, mais son résultat peut être stocké dans un cache CORS dédié, séparé du cache HTTP normal. [5][10]

Les principaux headers CORS

HeaderDirectionRôle
OriginRequêteOrigin qui initie la requête
Access-Control-Allow-OriginRéponseOrigin dont le code peut recevoir la réponse
Access-Control-Allow-MethodsRéponse preflightMéthodes autorisées par le preflight
Access-Control-Allow-HeadersRéponse preflightHeaders de requête autorisés par le preflight
Access-Control-Allow-CredentialsRéponseAutorisation d'exposer une réponse avec credentials
Access-Control-Expose-HeadersRéponseHeaders de réponse supplémentaires visibles par JavaScript
Access-Control-Max-AgeRéponse preflightDurée de cache du résultat du preflight
Vary: OriginRéponseIndique au cache que la réponse varie selon Origin

`Origin` est le header de requête qui identifie l'origin initiateur. `Access-Control-Allow-Origin` est un header de réponse indiquant au navigateur si la réponse peut être partagée avec le code de cet origin. [1][2][8]

`Access-Control-Allow-Methods` et `Access-Control-Allow-Headers` sont particulièrement importants pour le preflight. `Access-Control-Allow-Credentials` autorise l'exposition d'une réponse avec credentials et `Access-Control-Expose-Headers` peut exposer des headers supplémentaires à JavaScript. [1][2]

Quand le serveur renvoie dynamiquement `Access-Control-Allow-Origin`, MDN recommande également `Vary: Origin`. [1]

`Access-Control-Allow-Origin` : wildcard ou origin explicite

`Access-Control-Allow-Origin: *` permet de partager une réponse avec tout origin pour les requêtes sans credentials. Cela peut être correct pour une API réellement publique. [2][8]

Pour des endpoints privés ou sensibles, l'ensemble autorisé doit rester minimal. MDN et OWASP recommandent des origins spécifiques lorsque l'accès global n'est pas nécessaire. [14][17]

Avec plusieurs origins autorisés, le serveur doit comparer l'`Origin` reçu à une allowlist et ne renvoyer que la valeur validée. Il ne faut pas refléter aveuglément n'importe quel origin. [8][16]

Cookies et credentials : source fréquente d'erreurs

Fetch peut utiliser `credentials: "include"` pour demander l'envoi de credentials sur une requête cross-origin. Cela ne suffit pas côté client. Le serveur doit renvoyer `Access-Control-Allow-Credentials: true` et un `Access-Control-Allow-Origin` explicite. `*` est interdit dans ce cas. [2][6][9]

Les cookies suivent aussi leurs propres règles. `SameSite` peut empêcher l'envoi d'un cookie sur une requête cross-site, et `SameSite=None` nécessite `Secure`. [13]

CORS et `SameSite` résolvent des problèmes différents. CORS peut être correct alors que le cookie n'est pas envoyé, ou le cookie peut être envoyé alors que la réponse reste inaccessible à JavaScript.

Pourquoi `mode: "no-cors"` ne corrige généralement rien

`fetch(..., { mode: "no-cors" })` ne contourne pas CORS lorsqu'une application doit lire la réponse. Les méthodes et headers sont limités, et la réponse devient opaque. JavaScript ne peut lire ni body ni headers, et le statut exposé vaut `0`. [6][7][11]

`no-cors` a des usages spécialisés, notamment certains cas Service Worker, mais c'est généralement une mauvaise réponse pour une API JSON classique. [6]

Si l'API externe ne propose pas CORS, un backend ou proxy que vous contrôlez peut effectuer la requête serveur à serveur. [11][12]

Pourquoi curl, Postman ou un backend peuvent fonctionner

La same-origin policy et CORS sont appliqués par le navigateur. Un client HTTP hors de ce modèle n'est pas soumis au même contrôle. C'est pourquoi un endpoint peut fonctionner avec curl ou un outil API tout en étant bloqué depuis le JavaScript d'une page. [2][3][16]

Cela montre aussi que CORS n'est pas une authentification API. Un client hors navigateur peut choisir son propre `Origin`, donc OWASP déconseille d'utiliser ce header comme preuve d'identité. [16]

L'API a toujours besoin d'autorisation, tokens, sessions, contrôles de droits et validation côté serveur.

CORS ne remplace ni CSRF ni l'autorisation

La same-origin policy limite surtout la lecture cross-origin. Les formulaires et certaines requêtes simples peuvent néanmoins être envoyés depuis un autre site. Une application basée sur les cookies a donc toujours besoin de protections CSRF. [3][15]

OWASP recommande explicitement de ne pas utiliser CORS ou `Origin` seuls comme contrôle d'accès à des ressources sensibles. Authentification et autorisation restent nécessaires. [16]

CORS répond à la question de savoir si le navigateur peut exposer une réponse au code d'un origin. Il ne dit pas si l'utilisateur ou le client est autorisé à effectuer l'opération.

Erreurs fréquentes et diagnostic

L'absence de `Access-Control-Allow-Origin` est une erreur courante. D'autres causes sont une méthode interdite, un header manquant dans `Access-Control-Allow-Headers`, l'utilisation de `*` avec credentials, une mauvaise réponse OPTIONS ou des redirects dans le flux CORS. [11][12]

JavaScript reçoit volontairement peu de détails. La console et le panneau Network du navigateur donnent les informations utiles. [1][11]

`CORS request did not succeed` peut aussi provenir de DNS, timeout, refus de connexion, TLS, mixed content ou extensions. [11]

  • Vérifier l'origin exact du frontend : schéma, hôte et port.
  • Inspecter `OPTIONS` s'il existe.
  • Contrôler le statut et tous les `Access-Control-Allow-*` du preflight.
  • Vérifier aussi les headers CORS de la réponse réelle.
  • Avec cookies, vérifier `credentials`, `Access-Control-Allow-Credentials`, `SameSite` et `Secure`.
  • Contrôler redirects, TLS, mixed content et erreurs réseau.
  • Comparer la requête navigateur à une requête curl ou outil API qui fonctionne.

Configurations CORS dangereuses

Refléter sans validation l'`Origin` reçu est dangereux lorsque des requêtes avec credentials renvoient des données sensibles. Un attaquant peut contrôler un origin et tenter de lire des réponses dans le contexte d'un utilisateur connecté. [16]

Des expressions régulières trop larges et la confiance dans tous les sous-domaines sont aussi risquées si l'un d'eux peut être compromis. OWASP recommande une allowlist avec correspondance précise. [16][17]

`Access-Control-Allow-Origin: *` n'est pas automatiquement une faille pour des données entièrement publiques sans credentials. Le problème est une politique plus permissive que nécessaire. [8][17]

Performance : coût du preflight et `Access-Control-Max-Age`

Le preflight ajoute un `OPTIONS` avant la vraie requête et peut donc augmenter la latence. Son résultat peut être mis en cache avec `Access-Control-Max-Age` dans un cache séparé du cache HTTP normal. [5][10]

MDN indique une valeur par défaut de 5 secondes en l'absence du header. Les navigateurs imposent leurs propres maximums. Firefox limite à 86400 secondes et Chromium depuis la version 76 à 7200 secondes. [10]

Il est utile de mettre en cache une politique stable, mais pas au point de rendre difficile le retrait rapide d'une règle trop permissive.

Configuration pratique et checklist de déploiement

Commencez par décider si l'endpoint doit réellement être cross-origin. Sinon, n'ajoutez pas de headers CORS. Si oui, définissez les origins, méthodes et headers nécessaires ainsi que le besoin éventuel de credentials. [14][17]

Pour une allowlist dynamique, ne renvoyez qu'un origin validé et ajoutez `Vary: Origin`. Avec credentials, utilisez un origin explicite et `Access-Control-Allow-Credentials: true`. Le frontend ne peut pas remplacer cette décision serveur. [1][2]

Après déploiement, testez une requête simple, un preflight, un scénario cookie, un origin refusé et les erreurs réelles dans DevTools.

  • L'accès cross-origin est-il réellement nécessaire ?
  • Quels origins précis sont autorisés ?
  • Les credentials sont-ils nécessaires ?
  • Quelles méthodes et quels headers de requête sont nécessaires ?
  • `OPTIONS` atteint-il l'application et répond-il correctement ?
  • La réponse réelle contient-elle aussi les bons headers CORS ?
  • Les réponses dynamiques incluent-elles `Vary: Origin` ?
  • L'endpoint exige-t-il toujours authentification et autorisation normales ?
  • La politique a-t-elle été testée avec un origin autorisé et un origin refusé ?

CORS doit être compris comme une règle du navigateur pour l'accès aux réponses, pas comme une frontière universelle de sécurité de l'API. Il faut d'abord identifier les origins du frontend et de l'API, déterminer si la requête est simple ou nécessite un preflight, puis examiner la réponse OPTIONS et les headers CORS de la réponse réelle. Une configuration sûre n'autorise que les origins, méthodes et headers réellement nécessaires, tandis que l'authentification et l'autorisation restent dans l'application.

CORS Same-Origin Policy Web Security HTTP API Fetch API Preflight Cookies CSRF Frontend

Questions fréquentes

Qu'est-ce que CORS ?

CORS est un protocole HTTP utilisé par les navigateurs pour contrôler le partage de réponses entre origins. Le serveur indique par headers quels origins peuvent lire une réponse. [1][2]

Pourquoi l'API fonctionne-t-elle dans Postman mais pas dans le navigateur ?

Parce que CORS est appliqué par le navigateur dans le cadre de la same-origin policy. Les clients HTTP hors navigateur n'y sont pas soumis de la même manière. [2][3][16]

CORS empêche-t-il toujours l'envoi de la requête ?

Non. Une requête simple peut être envoyée puis sa réponse masquée à JavaScript. Si un preflight obligatoire échoue, la vraie requête n'est pas envoyée. [1][5][6]

Qu'est-ce qui déclenche un preflight ?

Par exemple des méthodes autres que GET, HEAD, POST, des headers personnalisés ou Content-Type: application/json. Le navigateur envoie d'abord OPTIONS. [1][5]

Faut-il ajouter Access-Control-Allow-Origin dans le frontend ?

Non. C'est un header de réponse serveur. Le frontend ne peut pas s'accorder lui-même cette permission. [1][8][12]

mode: "no-cors" corrige-t-il CORS ?

Pas pour une API dont il faut lire les données. La réponse devient opaque et son body, ses headers et son statut normal sont inaccessibles. [6][7]

Peut-on utiliser Access-Control-Allow-Origin: * ?

Oui pour des réponses publiques sans credentials. Pas pour exposer des réponses avec credentials. [2][8][9]

Comment CORS fonctionne-t-il avec les cookies ?

Le frontend utilise souvent credentials: "include", tandis que le serveur renvoie un origin explicite et Access-Control-Allow-Credentials: true. Le cookie reste soumis à SameSite et Secure. [6][9][13]

CORS protège-t-il une API contre curl ou les bots ?

Non. CORS limite l'accès JavaScript dans le navigateur. Un client externe peut envoyer sa propre requête et son propre Origin, donc l'API doit toujours autoriser ses clients. [16]

CORS protège-t-il contre CSRF ?

Pas complètement. Les applications basées sur les cookies ont toujours besoin de protections CSRF. [3][15][16]

À quoi sert Vary: Origin ?

Quand Access-Control-Allow-Origin varie dynamiquement, Vary: Origin informe les caches que la réponse dépend de l'origin demandeur. [1]

Comment réduire les preflights ?

On peut mettre leur résultat en cache avec Access-Control-Max-Age ou utiliser des requêtes simples lorsque cela reste approprié fonctionnellement et en sécurité. [1][10][11]

Sources et références

  1. MDN, Cross-Origin Resource Sharing (CORS)123456789101112131415
  2. WHATWG, Fetch Standard, CORS protocol123456789101112
  3. MDN, Same-origin policy123456
  4. MDN, Origin12
  5. MDN, Preflight request1234567
  6. MDN, Using the Fetch API12345678
  7. MDN, RequestInit12
  8. MDN, Access-Control-Allow-Origin123456
  9. MDN, Access-Control-Allow-Credentials123
  10. MDN, Access-Control-Max-Age1234
  11. MDN, CORS errors1234567
  12. MDN, CORS header Access-Control-Allow-Origin missing123
  13. MDN, Set-Cookie123
  14. MDN, Practical CORS configuration12
  15. MDN, Cross-site request forgery (CSRF)12
  16. OWASP, HTML5 Security Cheat Sheet, Cross Origin Resource Sharing123456789
  17. OWASP, HTTP Headers Cheat Sheet, Access-Control-Allow-Origin1234

Cela vous a-t-il été utile ?

Recevez les nouveaux articles par e-mail

Un court e-mail par nouvel article d'apprentissage. Pas de spam, désinscription en un clic.

Nous utilisons uniquement votre e-mail pour envoyer de nouveaux articles. Aucun partage avec des tiers.

Retour à l'apprentissage