Skip to Content

Chapitre 11.6 — OpenAPI, versioning & CORS

Partie 11 : API Platform — Chapitre 6/8 Version de référence : API Platform 4 / NelmioCorsBundle / Symfony 7.4.

⏱️ TL;DR

  • API Platform génère une spec OpenAPI complète (Swagger UI sur /api, spec exportable en JSON/YAML) — utilisable pour générer un client TypeScript (chapitre 11.8).
  • On enrichit la doc : descriptions, exemples, résumés d’opérations, via les attributs.
  • Versioning : préférez ne pas casser (ajouter des champs, déprécier en douceur) ; sinon versionnez par URL (/api/v1) ou par média-type.
  • CORS : dès que le front (localhost:3000) appelle l’API (localhost:8000), le navigateur exige le CORS. On configure NelmioCorsBundle : origines autorisées, méthodes, en-têtes (Authorization), credentials.
  • Le navigateur envoie une requête préliminaire OPTIONS (preflight) — l’API doit y répondre correctement.
  • Pour un dev Next.js : le CORS est obligatoire en dev (origines différentes) ; en prod, on peut l’éviter en passant par un proxy Next.js (même origine).

🎯 Objectifs

  • Exploiter et enrichir la doc OpenAPI.
  • Choisir une stratégie de versioning.
  • Configurer le CORS pour le front Next.js.
  • Comprendre le preflight OPTIONS.

1. La documentation OpenAPI

API Platform produit une spec OpenAPI (anciennement Swagger) décrivant toutes vos opérations, schémas, filtres, codes de réponse. Elle alimente :

  • la Swagger UI interactive sur /api (tester dans le navigateur) ;
  • la spec brute, exportable : GET /api/docs.json (ou .yaml) ;
  • une UI ReDoc alternative.
php bin/console api:openapi:export --yaml --output=openapi.yaml

Cette spec est un contrat machine : on la donne à un générateur pour produire un client TypeScript typé côté Next.js (chapitre 11.8).

2. Enrichir la doc

On documente les resources et opérations via les attributs :

use ApiPlatform\Metadata\{ApiResource, Get}; #[ApiResource( description: 'Une réservation de créneau chez un prestataire.', operations: [ new Get( summary: 'Récupère une réservation', description: 'Retourne la réservation si l’utilisateur y a accès.', ), ], )] class Booking { /* ... */ }

On ajoute des exemples de payloads, des descriptions de champs, et on masque les endpoints internes (#[ApiResource(..., openapi: false)] pour ne pas documenter une resource technique). Une bonne doc OpenAPI = un front plus rapide à intégrer (et un client généré plus riche).

3. Versioning

Une API consommée par plusieurs clients (web, mobile, partenaires) évolue sans les casser. Trois approches, par ordre de préférence :

  1. Ne pas casser (le plus sain) : ajouter des champs (optionnels), déprécier avant de retirer, utiliser les groupes pour faire cohabiter d’anciennes/nouvelles formes. On évite ainsi tout versioning explicite tant que possible — comme la BC promise de Symfony (Partie 1).
  2. Par URL : /api/v1/bookings, /api/v2/bookings. Explicite, simple à router, mais duplique.
  3. Par média-type / en-tête : Accept: application/vnd.booklab.v2+json. Élégant mais moins visible.

⚠️ Piège : versionner trop tôt (v1/v2 dès le départ) alourdit tout. Commencez sans version et faites évoluer par ajouts non cassants (nouveaux champs, nouvelles opérations). Ne sortez le versioning explicite que lorsqu’un vrai changement cassant est inévitable. La plupart des évolutions n’en ont pas besoin.

4. Le CORS : indispensable pour Next.js

Le CORS (Cross-Origin Resource Sharing) est une sécurité du navigateur : par défaut, un script sur http://localhost:3000 (front Next.js) ne peut pas lire une réponse de http://localhost:8000 (API Symfony) — origines différentes. L’API doit explicitement autoriser l’origine du front.

composer require nelmio/cors-bundle
# config/packages/nelmio_cors.yaml nelmio_cors: defaults: origin_regex: true allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] # ex. ^https?://localhost:3000$ allow_methods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'OPTIONS'] allow_headers: ['Content-Type', 'Authorization'] expose_headers: ['Link'] max_age: 3600 paths: '^/api': allow_credentials: true # si cookies (httpOnly) échangés
# .env CORS_ALLOW_ORIGIN=^https?://localhost:3000$

Points clés :

  • allow_origin : l’origine exacte du front (jamais * avec des credentials).
  • allow_headers doit inclure Authorization (pour le Bearer JWT) et Content-Type.
  • allow_credentials: true si le front envoie des cookies (auth par cookie httpOnly).

⚠️ Piège * + credentials : le navigateur interdit Access-Control-Allow-Origin: * avec allow_credentials: true. Si vous authentifiez par cookie, vous devez spécifier l’origine exacte (pas *). Et ne mettez jamais * en production « pour que ça marche » : listez les origines autorisées. Un CORS trop permissif est une faille.

5. Le preflight OPTIONS

Pour les requêtes « non simples » (avec Authorization, PATCH, JSON…), le navigateur envoie d’abord une requête OPTIONS (le preflight) pour demander « ai-je le droit ? ». L’API (via NelmioCors) répond avec les en-têtes Access-Control-Allow-*. Si le preflight échoue, la vraie requête n’est jamais envoyée.

6. Éviter le CORS en prod : le proxy Next.js

En production, on peut éliminer le CORS en servant l’API et le front sous la même origine : le front Next.js relaie les appels API via ses Route Handlers (ou rewrites), de sorte que le navigateur ne voit qu’une seule origine (https://booklab.app), et Next.js parle à l’API en server-to-server (pas de CORS). C’est souvent plus propre et plus sûr (le JWT reste côté serveur). On y revient au chapitre 11.8.

💡 Pour un dev Next.js : deux architectures. (A) Front appelle l’API directement depuis le navigateur → CORS obligatoire, JWT exposé au client. (B) Proxy Next.js (Route Handlers/rewrites) → même origine, pas de CORS, JWT en cookie httpOnly côté serveur Next. L’option B est recommandée pour la sécurité et la simplicité. Le CORS reste utile en dev (les deux serveurs sur des ports différents).

✏️ Exercices

Exercice 1. Votre front sur localhost:3000 reçoit une erreur CORS en appelant l’API sur localhost:8000. Quelles trois choses vérifiez-vous dans la config Nelmio ?

✅ Solution

(1) allow_origin inclut bien http://localhost:3000 (origine exacte). (2) allow_headers contient Authorization (et Content-Type). (3) allow_methods contient la méthode utilisée (POST/PATCH/DELETE) et OPTIONS (pour le preflight). Si des cookies sont échangés : allow_credentials: true et origine exacte (pas *). Vérifier aussi que le path ^/api est bien couvert.

Exercice 2. Pourquoi ne peut-on pas mettre allow_origin: '*' avec allow_credentials: true ?

✅ Solution

La spécification CORS interdit la combinaison Access-Control-Allow-Origin: * avec l’envoi de credentials (cookies). Le navigateur refuse alors la réponse. Avec des credentials, il faut spécifier l’origine exacte (http://localhost:3000), pas le joker *. C’est une protection contre l’exfiltration de données authentifiées vers n’importe quelle origine.

Exercice 3. Comment éviter complètement le CORS en production ?

✅ Solution

En servant front et API sous la même origine : le front Next.js proxifie les appels API via ses Route Handlers (ou des rewrites next.config), de sorte que le navigateur ne parle qu’à https://booklab.app (une seule origine → pas de CORS), et Next.js relaie vers l’API en server-to-server. Bonus : le JWT reste côté serveur Next (cookie httpOnly), jamais exposé au navigateur.

🧠 Quiz de révision

1. À quoi sert la spec OpenAPI au-delà de la doc ?

À générer un client TypeScript typé côté Next.js (contrat machine), en plus de documenter et tester l’API (Swagger UI).

2. Quelle stratégie de versioning privilégier ?

Ne pas casser : ajouter des champs, déprécier en douceur, cohabiter via les groupes. Le versioning explicite (URL /api/v1) seulement en dernier recours.

3. Pourquoi le CORS est-il nécessaire entre Next.js et l’API ?

Parce qu’ils sont sur des origines différentes (ports/domaines) : le navigateur bloque la lecture des réponses cross-origin sauf si l’API autorise explicitement l’origine (CORS).

4. Qu’est-ce que le preflight OPTIONS ?

Une requête préliminaire envoyée par le navigateur pour les requêtes non simples (Authorization, PATCH…) afin de vérifier les autorisations CORS avant la vraie requête.

5. Comment éviter le CORS en prod ?

En proxifiant l’API via Next.js (Route Handlers/rewrites) → même origine, pas de CORS, JWT côté serveur.


Prochain chapitre : 11.7 — GraphQL — exposer BookLab en GraphQL (optionnel).