Darylngako
16/10/20240 🇫🇷

Résoudre les problèmes de CORS en NextJS

Ecris par Ngako Daryl le 16 octobre 2024

On est déjà tous tombé ou presque sur ce genre d'erreur:

img-erreur CORS
Erreur CORS

Mais qu'est-ce que cela signifie ? Cela veut dire que tu ne peux pas accéder à la ressource demandée car tu n'as pas l'autorisation nécessaire (manque d'origine). Comment résoudre ce problème ? Tout d'abord, il est important de comprendre ce que signifie CORS.

C'est quoi les CORS?

Un CORS (Cross-Origin Ressource Sharing) est un mécanisme de sécurité utilisé par les navigateurs web pour prévenir les attaques de type Cross-Site Request Forgery (CSRF) et Cross-Site Scripting (XSS).

Comprendre le problème

Par défaut, les navigateurs web ne permettent pas à une page web d'accéder à des ressources (API, images,etc...) situées sur un autre domaine ou sous-domaine que celui de la page courante. C'est ça qu'on appele la politique de même origine(same-origin).

la politique de sécurité same-origin empêche les pages web de différentes origines d'accéder aux ressources les unes des autres.

Comment CORS résout-il le problème?

le CORS va donc permettre aux serveurs de spécifier laquelles des origines sont autorisées à accéder à leurs ressources. En d'autres termes CORS est comme un système de protection empêchant l'accès non autorisée aux ressources d'un domaine.

Comment fonctionnent-ils?

Lorsqu'un navigateur envoit une requête à un serveur, il inclut l'en-tête Origin avec l'origine de la requête. Le serveur répond par la suite avec les en-têtes CORS suivants:

  • Access-Control-Allow-Origin: spécifie les origines autorisées( "monporfolio.com", "monblog.com", etc... )
  • Access-Control-Alllow-Methods: indique les méthodes HTTP autorisées ( GET, POST, PUT, DELETE,etc.. )
  • Access-Control-Allow-Credentials: indique si le navigateur doit inclure des identifiants (cookies ou une authentification HTTP ) dans la demande d'origine croisée
  • etc...

Observe cette image... Tu vois:

  • l'Origine: ( yourdomain.com ) où est effectuée la requête
  • Google chrome: qui envoit la requête
  • un serveur CORS: ( example.com ) qui reçoit la requête et renvoit la réponse
img-fonctionnement des CORS
fonctionnement des CORS

Un Cross-Origin-Server ou serveur CORS est un serveur qui autorise les requêtes HTTP provenant de domaines différents( origines ).

Avant de pouvoir répondre à la requête demandée par le client, le serveur va devoir répondre à des requêtes préliminaires appelées préflight options pour vérifier les permissions.

Les préflight options (ou requêtes préliminaires ) sont des requêtes HTTP envoyées par le navigateur avant d'envoyer la requête principale afin de vérifier les permissions et les options de sécurité pour une requête CORS.

Comment tout celà fonctionne t-il?...

Google chrome vérifie d'abord si la requête émise par le client ( yourdomaine.com ) est autorisée en envoyant une requête préliminaire ( preflight ) au serveur ( en 1 ).

Le serveur ( example.com ) répond alors à cette requête préliminaire en envoyant des en-têtes HTTP qui indiquent les permissions et les options de sécurité pour la requête principal ( en 2 ).

Si la réponse préliminaire est positive, chrome envoit alors la requête principale sinon, il affiche une erreur CORS ( en 3 ).

Le serveur répond ( en 4 ).

Prenons un exemple...

Imaginons deux applications, l'un étant ton blog qui contient des posts et l'autre ton porfolio. Dans une section de ton porfolio tu souhaites afficher les posts( les plus récents ) de ton blog. Tu vas devoir donc créer une API dans ton blog et l'utiliser dans ton porfolio afin de récuperer cela. Tu procèdes comme suit:

  1. création de l'API
api/posts/route.ts
ts
import { getPosts } from "...";
import { sortPosts } from "...";
import { NextResponse, NextRequest } from "next/server";
 
export async function GET(req: NextRequest) {
  const posts = getPosts();
  const recentPosts = posts.slice(0, 3);
  console.log(JSON.stringify(recentPosts));
  return NextResponse.json({ posts: recentPosts });
}
  1. utilisation de l'API
ts
import { PostsResponseSchema } from "@/features/posts/posts.schema";
export async function getLatestPosts() {
  const options = {
    method: "GET",
    headers: {
      accept: "application/json",
    },
  };
  const response = await fetch("https://monblog.com/api/posts", options)
    .then((response) => response.json())
    .then(PostsResponseSchema.parse);
  console.log(response);
  return response;
}
ts
"use client";
import React from "react";
import {
  Card,
  CardTitle,
  CardDescription,
  CardHeader,
} from "@/components/ui/card";
import { useQuery } from "@tanstack/react-query";
import { getLatestPosts } from "@/components/utils/function";
import Loader from "@/components/shared/loader"
 
export const CardLatestPosts = () => {
  const { data, isLoading, isError } = useQuery({
    queryFn: async () => await getLatestPosts(),
    queryKey: ["posts"]
  });
  console.log("data", data);
  if (isError) return <p>Erreur</p>;
  if (isLoading) return <Loader size="small" />;
  return (
    <div className="grid grid-cols-1 gap-8">
      {data?.posts.map((post, _) => (
        <Card key={post.title} className="group h-48 rounded-xl border border-black/[0.2] dark:border-white/[0.2]">
            <CardHeader>
              <CardTitle className="group-hover:opacity-0  justify-between font-bold text-lg dark:text-foreground text-primary">
                {post.title}
              </CardTitle>
              <CardDescription className="font-semibold truncate">
                {post.description}
              </CardDescription>
            </CardHeader>
        </Card>
      ))}
    </div>
  )
}

Et maintenant regardons le résultat dans la console du navigateur:

img-exemple CORS
Exemple requête CORS

la variable datas est undefined et nous avons un message d'erreur. Mais pourquoi? Tout simplement parce que depuis l'autre application(le porfolio en question) tu n'as pas accès à ces tes posts.

Mais comment la ressource est protégée? On a juste créer une API puis l'utiliser. Enfaite par défaut tu ne peux pas accéder à une ressource distante ( située sur un autre domaine ). tu devra donc configurer les CORS dans ton application afin de la rendre accessible.

Comment configurer les CORS dans une application NextJS?

Il existe plusieurs façons de configurer les CORS en nextjs. Nous en parlerons de trois uniquement:

  • Dans le fichier next.config.js
  • Dans le fichier middleware.ts
  • Dans une API route

utilisation dans le fichier next.config.js

Tu peux directement ajouter la configuration des CORS dans ce fichier. Pour définir des en-têtes HTTP personnalisées, utilise la clé headers dans next.config.js:

next.config.js
ts
/** @type {import('next').NextConfig} */
const nextConfig = {
  async headers() {
    return [
      {
        // matching all API routes
        source: "/api/:path*",
        headers: [
          { key: "Access-Control-Allow-Credentials", value: "true" },
          { key: "Access-Control-Allow-Origin", value: "*" }, // wildcard(*) means that all domains can be there
          {
            key: "Access-Control-Allow-Methods",
            value: "GET,DELETE,PATCH,POST,PUT",
          },
        ],
      },
    ];
  },
};
module.exports = nextConfig;

N'oublies pas de redémarrer ton serveur NextJS après avoir appliquer les changements.

Utilisation du fichier middleware.ts

Le middleware en nextjs est une fonction qui s'éxécute avant qu'une requête ne soit terminée. Tu peux définir des en-têtes CORS dans le middleware pour autoriser les requêtes d'origine croisée.

middleware.ts
ts
import { NextRequest, NextResponse } from "next/server";
 
const corsOptions = {
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
  "Access-Control-Allow-Headers": "Content-Type, Authorization",
  "Access-Control-Allow-Origin": "*",
};
export function middleware(request: NextRequest) {
  const response = NextResponse.next();
  Object.entries(corsOptions).forEach(([key, value]) => {
    response.headers.set(key, value);
  });
  return response;
}
 
export const config = {
  matcher: "/api/:path*",
};

Utilisation dans une API route

Tu peux configurer les en-têtes CORS headers pour chaque route individuellment.

ts
export async function GET(request: Request) {
  return new Response("Hello, C'est Daryl!", {
    status: 200,
    headers: {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
      "Access-Control-Allow-Headers": "Content-Type, Authorization",
    },
  });
  //or
}

Autoriser plusieurs origines CORS

L'en-tête Access-Control-Allow-Origin est une option binaire qui accepte soit une seule origine, soit plusieurs origines. Tu peux utiliser un astérisque * pour définir cet en-tête afin qu'il accepte tous les domaines. Si tu souhaites autoriser plusieurs origines CORS, mais pas toutes, tu dois écrire une logique personnalisée.

Modification du fichier middelware.ts

middleware.ts
ts
import { NextRequest, NextResponse } from "next/server";
// the list of all allowed origins
const allowedOrigins = [
  "http://localhost:3000",
  "https://monblog.com",
  "https://monsite.com",
];
const corsOptions = {
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
  "Access-Control-Allow-Headers": "Content-Type, Authorization",
  "Access-Control-Allow-Origin": "*",
};
export function middleware(request: NextRequest) {
  // retrieve the current response
  const res = NextResponse.next();
 
  // retrieve the HTTP "Origin" header
  // from the incoming request
  req.headers.get("origin");
  // if the origin is an allowed one,
  // add it to the 'Access-Control-Allow-Origin' header
  if (allowedOrigins.includes(origin)) {
    res.headers.append("Access-Control-Allow-Origin", origin);
  }
  Object.entries(corsOptions).forEach(([key, value]) => {
    res.headers.append(key, value);
  });
  return response;
}
 
export const config = {
  matcher: "/api/:path*",
};

Utilisation de Nextjs-CORS package

nextjs-CORS est un package qui facilite la configuration et la gestion des CORS dans une application nextjs. Tu peux installer le package nextjs-cors en exécutant la commande suivante dans ton application:

bash
 npm install nextjs-cors

Ensuite utilise le comme ceci dans ton API:

ts
import { NextResponse } from "next/server";
import Cors from "nextjs-cors";
 
export async function POST(request: Request) {
  // Appliquer les en-têtes CORS sur cette route
  await Cors(request, NextResponse, {
    methods: ["GET", "POST"],
    origin: ["https://monsite.com"], // Limiter l'origine
    optionsSuccessStatus: 200,
  });
 
  return NextResponse.json({ message: "CORS configuré" });
}

Conclusion

En résumé, CORS est un mécanisme de sécurité qui pemret aux serveurs de contrôler l'accès à des ressources depuis des origines différentes. En nextjs il existe plusieurs façons de les configurer que ce soit dans une API route, dans le fichier config.js ou middleware.ts. Tu peux aussi utiliser le package next-js CORS pour configurer plus facilement les CORS.

Donc je compte sur toi pour maintenant sécuriser tes applications😉.