Accueil >PHP >API REST en PHP >CORS : appeler l’API depuis JavaScript

CORS : appeler l’API depuis JavaScript

Le JavaScript d’une page ne peut appeler librement qu’une API à la même adresse que la page. Si l’API se trouve ailleurs, le serveur doit l’autoriser avec les en-têtes CORS. Comment décide le navigateur, ce qu’est le preflight et comment le configurer correctement en PHP.

La politique de même origine (same-origin policy)

L’origine est un triplet : schéma, domaine et port, par exemple https://www.example.com. Le navigateur ne permet à un script de lire une réponse que de la même origine. Sont aussi une autre origine :

  • https://api.example.com – un autre sous-domaine ;
  • http://www.example.com – un autre schéma ;
  • https://www.example.com:8443 – un autre port.

Cette règle protège les visiteurs : sinon, une page étrangère qu’ils ont ouverte pourrait, en leur nom (avec leurs cookies), lire les données de leur banque en ligne ou du réseau de leur entreprise.

CORS (Cross-Origin Resource Sharing) est la façon dont le serveur autorise une exception. Le plus simple est pourtant de ne pas avoir besoin de CORS du tout : si le site et l’API fonctionnent sous la même origine (https://www.example.com/api/…), aucun en-tête n’est nécessaire.

Comment décide le navigateur

Pour une requête vers une autre origine, le navigateur ajoute l’en-tête Origin avec l’origine de la page. Il ne transmet la réponse au script que si le serveur indique Access-Control-Allow-Origin avec cette origine (ou * = n’importe laquelle).

Une requête simple – GET, HEAD ou POST sans en-têtes personnalisés et avec un corps uniquement de type application/x-www-form-urlencoded, multipart/form-data ou text/plain – le navigateur l’envoie immédiatement.

Les autres requêtes – autres méthodes (PATCH, DELETE), type application/json, en-tête Authorization – le navigateur les annonce d’abord par une requête preflight : une requête OPTIONS avec les en-têtes Access-Control-Request-Method et Access-Control-Request-Headers. Il n’envoie la vraie requête que si la réponse au preflight autorise la méthode et les en-têtes. Un appel d’API REST avec JSON et une clé est donc toujours précédé d’un preflight.

Le preflight ne porte ni l’en-tête Authorization ni les cookies. Le serveur doit donc y répondre avant de vérifier la clé – sinon il renverrait 401 et le navigateur n’enverrait jamais la vraie requête.

Les en-têtes CORS en PHP

Dans le fichier index.php de l’article Méthodes HTTP et routage, ils se placent après la ligne avec $method, donc avant la vérification de la clé :

$allowed_origins = array("https://www.example.com", "https://admin.example.com");
$origin = $_SERVER["HTTP_ORIGIN"] ?? "";
header("Vary: Origin");
if(in_array($origin, $allowed_origins, true))
  {
    header("Access-Control-Allow-Origin: ".$origin);
    header("Access-Control-Expose-Headers: Location");
  }
if($method === "OPTIONS")
  {
    header("Access-Control-Allow-Methods: GET, POST, PATCH, DELETE");
    header("Access-Control-Allow-Headers: Content-Type, Authorization");
    header("Access-Control-Max-Age: 600");
    http_response_code(204);
    exit;
  }
  • Liste des origines autorisées. Le serveur renvoie dans Access-Control-Allow-Origin l’origine reçue, mais seulement si elle figure dans la liste. L’en-tête peut contenir une seule origine ou *, une liste n’est pas possible.
  • Vary: Origin indique aux caches (navigateurs, serveurs proxy, CDN) que la réponse dépend de l’en-tête Origin. Sans lui, la réponse destinée à un site pourrait passer du cache à un autre.
  • Chaque réponse a les en-têtes CORS, y compris les réponses d’erreur. Sans eux, JavaScript recevrait, au lieu d’une erreur 401 ou 422 compréhensible, une simple erreur réseau.
  • Access-Control-Expose-Headers. Sans autorisation, un script ne peut lire que quelques en-têtes de réponse de base (Content-Type, Content-Length, Cache-Control, Content-Language, Expires, Last-Modified, Pragma). Location, avec l’adresse de la nouvelle tâche, doit être autorisé.
  • Access-Control-Max-Age – combien de secondes le navigateur retient la réponse au preflight. Sans lui, il envoie un preflight avant presque chaque requête (la durée par défaut est de 5 secondes). Les navigateurs limitent les durées plus longues : Chrome à 2 heures, Firefox à 24 heures (état en octobre 2026).

Réponse au preflight depuis une origine autorisée :

curl -i -X OPTIONS http://localhost/api/tasks -H "Origin: https://www.example.com" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: content-type, authorization"
HTTP/1.1 204 No Content
Vary: Origin
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Expose-Headers: Location
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 600

L’appel depuis JavaScript

Une fonction pour appeler l’API avec fetch :

async function api(method, path, data) {
  const response = await fetch("https://api.example.com/api" + path, {
    method: method,
    headers: { "Content-Type": "application/json", "Authorization": "Bearer " + key },
    body: data === undefined ? undefined : JSON.stringify(data),
  });
  if (response.status === 204) return null;
  const body = await response.json();
  if (!response.ok) throw new Error(body.detail ?? "Erreur " + response.status);
  return body;
}
  • fetch ne lève une exception qu’en cas d’erreur réseau ou de blocage par CORS. Une réponse 404 ou 500 est un succès pour fetch – l’erreur, vous devez la reconnaître d’après response.ok (code 200–299) ou response.status.
  • La réponse 204 n’a pas de corps, response.json() échouerait sur elle.
  • La variable key contient la clé API. N’écrivez jamais la clé dans le code de la page – n’importe quel visiteur peut lire le code source du JavaScript. Dans le navigateur, seule a sa place une clé que saisit l’utilisateur (par exemple un administrateur dans un formulaire), ou, au lieu d’une clé, une connexion avec session.
const task = await api("POST", "/tasks", { title: "Appeler l’API depuis le navigateur" });
console.log(task.id, task.title);
try {
  await api("DELETE", "/tasks/999");
} catch (e) {
  console.log(e.message);
}
1 Appeler l’API depuis le navigateur
La tâche 999 n’existe pas.

Si l’origine de la page ne figure pas dans la liste, le navigateur bloque dès le preflight et fetch se termine par l’exception TypeError. Son texte varie : Chrome écrit Failed to fetch, Firefox NetworkError when attempting to fetch resource. et Safari (WebKit) Load failed (état en octobre 2026). La cause n’apparaît que dans la console du navigateur, le script ne peut pas la connaître.

Cookies et credentials

Sans réglage supplémentaire, fetch n’envoie pas de cookies vers une autre origine. Si l’API reconnaît l’utilisateur par sa session, les deux côtés doivent s’accorder :

  • JavaScript : fetch(url, { credentials: "include" }) ;
  • serveur : Access-Control-Allow-Credentials: true et une origine concrète dans Access-Control-Allow-Origin. Avec l’astérisque, le navigateur bloque la réponse.

Un sous-domaine (api.example.com pour un site sur www.example.com) est une autre origine, mais le même site, c’est pourquoi le cookie avec l’attribut SameSite=Lax est aussi envoyé. Vers un domaine tout à fait différent, le navigateur n’envoie que le cookie avec les attributs SameSite=None et Secure. Une API qui travaille avec des cookies a alors aussi besoin d’une protection contre le CSRF (voir Protéger les formulaires contre le CSRF). Une clé dans l’en-tête Authorization n’a pas ce problème : le navigateur ne l’ajoute jamais de lui-même.

CORS ne protège pas le serveur

CORS décide seulement si un script dans le navigateur peut lire la réponse. Il ne vérifie pas qui appelle l’API :

  • curl, PHP ou n’importe quel autre programme envoie l’en-tête Origin qu’il veut, ou aucun – et reçoit toujours la réponse ;
  • une requête simple (par exemple un POST depuis un formulaire), le navigateur l’envoie aussi à une API qui n’autorise pas CORS. Il ne bloque que la lecture de la réponse – le serveur a pourtant reçu et exécuté la requête.

L’accès aux données est donc toujours protégé par une clé ou une connexion (voir Authentification de l’API : clés et jetons) et par la validation sur le serveur. Les en-têtes CORS permettent seulement aux sites autorisés d’utiliser l’API confortablement.