Cet article s’adresse à toute personne qui gère une application web échangeant des fichiers avec un bucket Amazon S3, et il vous montre comment corriger les erreurs CORS. CORS, ou cross-origin resource sharing (partage de ressources entre origines), est la règle du navigateur qui décide si une page peut lire un fichier servi par un autre domaine. Vous la rencontrez sous la forme du message « No ‘Access-Control-Allow-Origin’ header is present » dans la console, juste après qu’un téléversement ou une image cesse de fonctionner. Presque tous les cas viennent d’un bucket sans configuration CORS, de règles qui ne correspondent pas exactement à la requête, ou d’une réponse en cache servie par CloudFront sans en-têtes CORS. Vérifiez d’abord le statut de la réponse, car un 403 est un problème de permissions déguisé en CORS qu’aucune règle CORS ne corrigera.
Ce qui se passe
CORS est un mécanisme appliqué par le navigateur, pas un contrôle de sécurité sur le bucket. Quand du JavaScript sur https://app.example.com demande un objet sur https://my-bucket.s3.ca-central-1.amazonaws.com, le navigateur exige que la réponse de S3 inclue Access-Control-Allow-Origin correspondant à l’origine de la page. Certaines requêtes ne sont pas « simples », comme un téléversement PUT ou tout ce qui porte des en-têtes personnalisés tels que x-amz-meta-* ou un Content-Type explicite. Dans ces cas, le navigateur envoie d’abord une requête préliminaire OPTIONS (preflight). S3 doit y répondre avec les méthodes et en-têtes permis avant même que la vraie requête soit tentée.
S3 n’émet des en-têtes CORS que si deux conditions tiennent : la requête porte un en-tête Origin, et cette origine correspond à une entrée AllowedOrigins exactement — schéma inclus, port inclus, pas de barre oblique finale. https://app.example.com ne correspond pas à http://app.example.com ni à https://www.app.example.com. Un joker * couvre tout mais ne se combine pas aux requêtes avec identifiants, et https://*.example.com (un joker permis par origine) couvre les sous-domaines.
Le piège qui survit à une configuration correcte : CloudFront. Si la politique de cache de la distribution n’inclut pas Origin dans la clé de cache et ne le transmet pas en amont, CloudFront met en cache une seule réponse — souvent une réponse obtenue sans en-tête Origin, donc sans en-têtes CORS — et la sert ensuite à tous les navigateurs.
Quoi vérifier en premier
- La réponse qui échoue est-elle en fait un 403 ou un 404? Ouvrez la requête échouée dans DevTools > Network. Un statut d’erreur veut dire que le problème est les permissions, pas CORS — les navigateurs rapportent tout statut d’erreur inter-origines comme un échec CORS.
- Le bucket a-t-il une configuration CORS tout court? Les nouveaux buckets n’en ont aucune.
- L’origine de la page correspond-elle à
AllowedOriginsoctet pour octet? Vérifiez schéma, hôte et port. - La requête passe-t-elle par CloudFront ou directement au point de terminaison S3? Le correctif diffère.
- Est-ce un échec de preflight? DevTools montre la requête
OPTIONSséparément — si c’est elle qui échoue, regardezAllowedMethodsetAllowedHeaders.
Comment vérifier
Lisez la configuration actuelle :
aws s3api get-bucket-cors --bucket my-bucket
Reproduisez le preflight sans navigateur — c’est la boucle de débogage la plus rapide :
curl -i -X OPTIONS "https://my-bucket.s3.ca-central-1.amazonaws.com/test.jpg" \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: PUT" \
-H "Access-Control-Request-Headers: content-type"
Une configuration fonctionnelle retourne 200 avec Access-Control-Allow-Origin, Access-Control-Allow-Methods et Access-Control-Allow-Headers renvoyés en écho. Un 403 avec AccessForbidden, ou une réponse sans en-têtes CORS, vous dit que la règle ne correspond pas à cette combinaison exacte. Testez aussi le cas GET simple :
curl -i "https://my-bucket.s3.ca-central-1.amazonaws.com/test.jpg" \
-H "Origin: https://app.example.com" | grep -i access-control
Chemin console : S3 > Buckets > votre-bucket > Permissions > Cross-origin resource sharing (CORS).
Le correctif
- Appliquez une configuration qui correspond à la réalité. Pour le cas courant — une application web qui télécharge et téléverse directement vers S3 (incluant les téléversements PUT présignés) :
[
{
"AllowedOrigins": ["https://app.example.com", "https://staging.example.com"],
"AllowedMethods": ["GET", "HEAD", "PUT", "POST"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag", "x-amz-version-id"],
"MaxAgeSeconds": 3000
}
]
Appliquez avec aws s3api put-bucket-cors --bucket my-bucket --cors-configuration file://cors.json. Notez que put-bucket-cors remplace la configuration entière — fusionnez, n’écrasez pas aveuglément.
-
Gardez
AllowedHeaderslarge pour les téléversements. Les PUT présignés envoientContent-Typeet souvent des en-têtesx-amz-*; les lister un à un est un tapis roulant d’entretien."AllowedHeaders": ["*"]est correct — rappelez-vous que CORS n’est pas votre frontière de sécurité. -
Ajoutez
ExposeHeaderspour tout ce que le JavaScript lit. Un code de téléversement multipart qui lit leETagde la réponse recevranulldans le navigateur siETagn’est pas exposé, même si la requête a réussi. -
Corrigez la couche CloudFront. Utilisez une politique de cache qui inclut l’en-tête
Origindans la clé de cache. Concrètement, prenez la politique de requête d’origine géréeCORS-S3Origin, plus une politique de cache avecOrigin,Access-Control-Request-MethodetAccess-Control-Request-Headers. Permettez ensuite la méthodeOPTIONSsur le comportement, puis invalidez après le changement, car les mauvaises réponses en cache survivent aux corrections de configuration. -
Si c’est un 403 en dessous, corrigez le 403. URL présignée expirée, signature qui ne correspond pas, Block Public Access — aucune règle CORS ne change un statut d’erreur.
Quand c’est la mauvaise approche
N’utilisez pas les règles CORS comme contrôle d’accès — elles n’instruisent que les navigateurs, et curl les ignore complètement. Et si l’objet est chargé via une balise <img>, un lien de téléchargement ou du code côté serveur, CORS n’est pas impliqué du tout ; déboguer là est du temps perdu. Si vous contrôlez les deux origines, envisagez de servir les fichiers par la même distribution CloudFront que l’application — des requêtes de même origine font disparaître tout le problème.
Erreurs fréquentes
- Une barre oblique finale ou un écart
www.dansAllowedOrigins— la correspondance est exacte, et l’échec ressemble en tout point à l’absence de configuration. - Tester avec
curlsans en-têteOrigin, ne voir aucun en-tête CORS, et conclure que la config est brisée — S3 ne les envoie que quand une origine est présentée. - Corriger le bucket sans invalider CloudFront, puis déclarer que le correctif a échoué.
- Mettre
MaxAgeSecondsà une journée pendant le débogage — le navigateur met en cache le vieux verdict de preflight et ignore votre correctif jusqu’à expiration.
Prévention
- Gardez la configuration CORS en IaC à côté de la définition du bucket, avec les origines de staging et de production listées explicitement.
- Ajoutez la vérification preflight
curl -X OPTIONSà vos tests de fumée de déploiement pour tout bucket touché par des navigateurs. - Routez le trafic navigateur par CloudFront avec une politique de cache consciente de CORS dès le premier jour plutôt que d’exposer les points de terminaison bruts du bucket.
- Documentez quelles applications téléversent directement vers quels buckets, pour qu’un changement d’origine (nouveau domaine, nouvel environnement) déclenche une mise à jour CORS. La référence complète du comportement CORS est sur https://docs.aws.amazon.com/AmazonS3/ dans la section CORS.
Les échecs CORS sont déterministes : correspondance exacte de l’origine, bonnes méthodes et bons en-têtes, et un regard honnête sur le statut sous-jacent. Parcourez ces trois points et ça fonctionne. Si personne dans votre équipe ne possède vraiment l’environnement AWS de bout en bout, c’est la racine de la plupart de ces problèmes — Stack Harbor opère des comptes AWS au quotidien sous Opérations AWS, et vous pouvez réserver une revue AWS.