Le ticket support arrive avec une capture d'écran : « CORS bloque notre API ». Le développeur frontend insiste pour que le backend « ouvre CORS en * ». En parallèle, curl sans header Origin fonctionne parfaitement. La confusion coûte des journées parce qu'on traite CORS comme une serrure serveur — alors que c'est une policy navigateur qui contrôle qui peut lire la réponse JavaScript.
CORS ne protège pas votre API des attaques directes : curl, Postman, scripts serveur et applications mobiles natives ignorent CORS. L'authentification (JWT, session, mTLS) reste indispensable. CORS répond à une question précise : est-ce que https://app.example.com a le droit d'exposer la réponse JSON à son JavaScript ?
Les incidents récurrents viennent du preflight OPTIONS renvoyé 401, du * incompatible avec cookies, ou de headers CORS dupliqués entre nginx et le framework. Voici comment structurer une policy claire sans ouvrir la porte à tout le web.
Preflight OPTIONS et méthodes
Les requêtes « non simples » (JSON POST, headers custom, PUT/DELETE) déclenchent une preflight OPTIONS. Le navigateur envoie Origin, Access-Control-Request-Method, parfois Access-Control-Request-Headers. Le serveur doit répondre 204/200 avec les headers CORS avant la vraie requête.
Erreur classique : le middleware d'authentification traite OPTIONS comme POST et renvoie 401. Le navigateur affiche une erreur CORS opaque — le développeur accuse CORS, la cause est l'auth.
Excluez OPTIONS du contrôle auth sur les routes API publiques. Testez avec :
curl -X OPTIONS -H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST" \
-i https://api.example.com/v1/resource
Origines, credentials et Vary
| Scénario | Access-Control-Allow-Origin | Credentials |
|---|---|---|
| API publique sans cookie | origine exacte ou liste | false |
| SPA avec cookie session | origine exacte whitelistée | true + Allow-Credentials |
* avec cookies | interdit par spec | échec navigateur |
Avec Access-Control-Allow-Credentials: true, renvoyez l'origine exacte de la requête si elle est dans votre whitelist — jamais *.
Ajoutez Vary: Origin sur les réponses CORS dynamiques pour éviter qu'un CDN ne serve la mauvaise origine en cache.
Une seule couche émet les headers
nginx et Express (ou Laravel, Django) qui ajoutent tous deux Access-Control-Allow-Origin produisent des headers dupliqués — certains navigateurs échouent de façon intermittente, le pire type de bug production.
Choisissez : CORS au edge (nginx/API gateway) ou dans l'application — pas les deux. Documentez dans l'ADR architecture.
Annual audit : grep config nginx + code middleware — une seule source de vérité.
CORS ≠ sécurité API
Un attaquant forge une requête depuis son serveur sans Origin navigateur — CORS ne s'applique pas. Rate limiting, auth, validation input, WAF restent nécessaires.
Document interne court : « Les apps mobiles natives n'ont pas besoin de CORS » — réduit les tickets faux positifs au backend.
OpenAPI : listez origines autorisées par environnement et exemple curl preflight copy-paste pour partenaires.
Tests et observabilité
Collection Postman partenaire maintenue en CI — régression CORS détectée pre-merge.
Métriques : ratio OPTIONS 204 vs 401, origines inconnues rejetées (log agrégé sans PII).
En cas d'incident : reproduisez d'abord en curl preflight, puis navigateur avec cache désactivé — évite les fausses pistes extension Chrome.
Revue gateway
Audit annuel une seule couche headers CORS — doublon nginx plus app casse navigateurs.
OPTIONS doit répondre vite — timeout ressemble CORS mais c'est lenteur serveur.
Apps natives mobile n'ont pas besoin CORS — doc interne.
Suivi opérationnel
CORS documenté OpenAPI réduit tickets support partenaires — investissement rentable. Exemple curl preflight copy-paste par environnement. Partner Postman collection maintained CI — CORS regression caught pre-merge. Documentez les écarts entre promesse hébergeur et mesure terrain dans la revue trimestrielle.
Poursuite trimestrielle
CORS documenté OpenAPI réduit tickets support partenaires — investissement rentable. Exemple curl preflight copy-paste par environnement. Partner Postman collection maintained CI — CORS regression caught pre-merge. Documentez les écarts entre promesse hébergeur et mesure terrain dans la revue trimestrielle.
Décider et avancer sans angle mort
- Reproduire preflight curl — OPTIONS avec Origin et Access-Control-Request-Method avant de toucher nginx.
- Whitelist origines explicites — jamais reflect arbitraire ;
Vary: Originsi CDN. - Skip auth sur OPTIONS — middleware dédié ; 401 sur preflight = ticket classique.
- Kit intégration partenaire — Origin exact, credentials, fetch exemple, 401 vs CORS browser.
- Auth API indépendante — JWT, rate limit, CSRF si cookies ; CORS n'est pas la serrure serveur.
API cross-domain : comparateur, annuaire, blog backend.
Questions fréquentes
CORS protège-t-il l'API des attaques ?
Non : il informe le navigateur quelles origines peuvent lire la réponse. curl, mobile natif et scripts serveur ignorent CORS — l'authentification API reste indispensable.
Pourquoi * avec cookies échoue ?
La spécification interdit Access-Control-Allow-Origin: * lorsque Access-Control-Allow-Credentials: true. Il faut renvoyer l'origine exacte depuis une whitelist.
Que faire quand OPTIONS renvoie 401 ?
Le middleware d'authentification bloque la preflight. Excluez OPTIONS du contrôle auth — cause fréquente de tickets « CORS cassé ».
Comment tester sans le navigateur ?
Reproduisez avec curl -X OPTIONS -H Origin:https://app.example.com -H 'Access-Control-Request-Method: POST' sur l'URL API.
Documentez les origines autorisées dans l'onboarding partenaire — moins de déploiements d'urgence vendredi soir.
