Aller au contenu principal

Réseaux & Web · L2 · Section 6/7

CORS (approfondi)

Progression

Points d’expérience : XPSérie de jours consécutifs : · —Progression du module : — / —compris

#CORS (approfondi)

Le navigateur impose la politique de même origine par défaut. CORS (Cross-Origin Resource Sharing) définit comment un serveur peut autoriser des requêtes depuis une autre origine de manière fine.

Objectifs d'apprentissage

  • Prédire si une requête déclenche un prévol OPTIONS, rien qu'en lisant méthode et en-têtes.
  • Écrire les en-têtes de réponse exacts et diagnostiquer un blocage CORS dans la console.
  • Ne jamais combiner Access-Control-Allow-Origin: * et des credentials.

#Prévol (preflight) et simples requêtes

Les simples requêtes (GET/HEAD/POST) sans en-têtes personnalisés et avec des types sûrs (application/x-www-form-urlencoded, multipart/form-data, text/plain) n'envoient pas de prévol. Dès que vous utilisez des en-têtes non simples (dont Authorization et Content-Type: application/json) ou des méthodes différentes (PUT, PATCH, DELETE), le navigateur émet un OPTIONS de prévol pour vérifier les autorisations.

En-têtes clés côté serveur:

  • Access-Control-Allow-Origin: origine autorisée ou * (interdit avec credentials).
  • Access-Control-Allow-Methods: méthodes permises.
  • Access-Control-Allow-Headers: en-têtes personnalisés permises.
  • Access-Control-Allow-Credentials: true: autorise cookies/Authorization; alors Allow-Origin doit être une origine exacte.
  • Access-Control-Max-Age: mise en cache du prévol par le navigateur.

#Trace exacte: prévol puis requête réelle

Requête initiale côté front (https://app.example.com), donc cross-origin vers https://api.example.com:

jsjs

1fetch('https://api.example.com/resource', {2  method: 'PUT',3  credentials: 'include',4  headers: { 'Content-Type': 'application/json', 'X-Trace-Id': 'abc123' },5  body: JSON.stringify({ v: 2 }),6})

Échange complet observé (DevTools, colonne Méthode; le prévol apparaît comme OPTIONS):

texttext

11) OPTIONS /resource HTTP/1.12   Host: api.example.com3   Origin: https://app.example.com4   Access-Control-Request-Method: PUT5   Access-Control-Request-Headers: content-type,x-trace-id6 72) HTTP/1.1 204 No Content8   Access-Control-Allow-Origin: https://app.example.com9   Access-Control-Allow-Methods: GET, POST, PUT, DELETE10   Access-Control-Allow-Headers: Content-Type, X-Trace-Id11   Access-Control-Allow-Credentials: true12   Access-Control-Max-Age: 60013   Vary: Origin14 

Lecture de la trace: (1) le navigateur, pas le développeur, émet le OPTIONS et y liste la méthode et les en-têtes non simples qu'il prévoit d'envoyer; (2) le serveur autorise précisément ces valeurs; le Vary: Origin évite qu'un cache serve renvoie l'autorisation d'une autre origine; (3) seulement alors la vraie requête part, identique à ce qui avait été annoncé; (4) la réponse réelle doit aussi porter Allow-Origin, sinon le blocage a lieu au moment de la lecture.

Si l'étape 2 renvoie un simple 404 ou 405 (route non déclarée pour OPTIONS), la console affiche « Response to preflight request didn't pass access control check: It does not have HTTP ok status »: la requête réelle ne partira jamais.

#Bonnes pratiques

Une politique CORS précise réduit la surface: répondez avec l'origine exacte attendue et limitez méthodes et en-têtes autorisés; variez la réponse sur Origin (Vary: Origin). Évitez les credentials pour des API publiques; si vous les utilisez, n'employez jamais * pour Allow-Origin. Pour des échanges inter-origines embarqués (iframes), préférez un protocole postMessage bien borné plutôt qu'une politique CORS large.

#Explorateur CORS interactif

Explorateur CORS (Origin, Credentials, Preflight)
Preflight: OKRequête: AUTORISÉE
Client (navigateur)
Origin
Méthode
Content-Type
Serveur (API)
Politique
Allow-Credentials
Allow-Methods
Allow-Headers
Expose-Headers
Preflight (OPTIONS)
Preflight: OK
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, X-Auth-Token
Credentials: include
Réponse préflight
Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, X-Auth-Token
Vary: Origin
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Réponse finale
Requête: AUTORISÉE
Access-Control-Allow-Origin: https://app.example.com
Vary: Origin
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: X-Request-ID
Rappel: avec credentials, ne pas utiliser `Access-Control-Allow-Origin: *`. Faites écho de l’origine autorisée et ajoutez Vary: Origin.
Conseils: préférez listes blanches par origin, exposez les en-têtes utiles via Expose-Headers, et gardez les règles cohérentes côté proxy/API et app. Les en-têtes de requête personnalisés déclenchent un preflight.

#Flow: décision CORS

Sur une page servie depuis https://app.example.com, déterminez sans exécuter si chaque appel ci-dessous déclenche un prévol, puis vérifiez dans l'explorateur ci-dessus:

  1. fetch('https://cdn.example.com/logo.png'). Correction: non. GET, aucun en-tête personnalisé: requête simple, le serveur n'a qu'à renvoyer Access-Control-Allow-Origin.
  2. fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' } }). Correction: oui. application/json n'est pas un type sûr pour un POST; prévol OPTIONS avec Access-Control-Request-Headers: content-type.
  3. fetch(url, { method: 'DELETE', credentials: 'include' }). Correction: oui. DELETE n'est jamais une méthode simple; et côté serveur Allow-Credentials: true exige une origine exacte.
  4. fetch(url, { headers: { 'X-Api-Key': 'k' } }). Correction: oui. X-Api-Key est un en-tête personnalisé, le prévol le liste dans Access-Control-Request-Headers.

#Diagnostic: trois blocages typiques et leur signature console

  • Absence d'Allow-Origin sur la réponse réelle: preflight réussi puis blocage à la lecture; console « No 'Access-Control-Allow-Origin' header is present ». Le prévol ne suffit pas, chaque réponse doit porter l'en-tête.
  • Origine non listée par le serveur: réponse 200 saine, lecture refusée; typique d'un serveur qui énumère des origines autorisées et oublie le port (https://app.example.com:3000 et https://app.example.com sont deux origines distinctes).
  • Erreur réseau pendant la requête CORS: la console mentionne un « réseau » et parfois CORS; vérifiez d'abord le serveur (curl), CORS ne fait que masquer le détail d'une panne sous-jacente.

#Atelier

Écrivez deux réponses: (1) API publique sans credentials avec Allow-Origin: https://app.exemple, (2) API privée avec cookies (Allow-Origin: https://admin.exemple, Allow-Credentials: true, Vary: Origin). Comparez le comportement du navigateur avec et sans Max-Age sur le prévol pour observer l'impact sur la latence perçue. Observation attendue: sans Access-Control-Max-Age, chaque navigation émet un OPTIONS visible dans l'onglet Réseau; avec Max-Age: 600, plus aucun OPTIONS pendant dix minutes.

#Diagramme: prévol puis requête

Navigateur
API
1. OPTIONS /resource (preflight)
2. 204 + ACAO/ACAM/ACAH + Max-Age
3. POST /resource (avec headers)
4. 200 + Access-Control-Allow-Origin
Une requête PUT avec Authorization est envoyée depuis une autre origine. Que fait le navigateur ?
Une requête PUT avec Authorization est envoyée depuis une autre origine. Que fait le navigateur ?
Le serveur répond Allow-Origin: * à un fetch credentials: 'include'. Résultat ?
Le serveur répond Allow-Origin: * à un fetch credentials: 'include'. Résultat ?

Mini-exercice: listez les en-têtes de votre requête front (méthode, headers). Déterminez si elle est « simple ». Si non, proposez une politique CORS minimale côté serveur (origines, méthodes, en-têtes, Max-Age) et vérifiez chaque valeur annoncée par le prévol (Access-Control-Request-*) contre la politique que vous avez écrite.