Logiciel & IA · De la stratégie à la production

Technologies · 14 octobre 2025 · 9 min de lecture

Sécuriser une application React : interface, API et Server Components

Sécuriser une application React en 2026, c’est traiter quatre surfaces : l’interface (injection de contenu), les sessions (où vivent les jetons), l’API (droits et validation côté serveur) et, si vous utilisez Next.js ou un autre framework avec React Server Components, le code qui s’exécute sur le serveur. Cette dernière surface est la plus récente et la plus critique : en décembre 2025, une faille des Server Components a permis d’exécuter du code à distance sur des serveurs sans aucune authentification.

La plupart des incidents ne viennent pas d’attaques sophistiquées, mais de réglages incomplets : un contenu non filtré qui devient script, un jeton laissé dans le localStorage, un CORS ouvert « le temps de tester », une Server Action qui ne vérifie pas les droits, un framework qui n’a pas été mis à jour. Voici les règles que nous appliquons sur nos projets React.

Sécuriser une application React : interface, API et Server Components

La carte des risques

Les risques se regroupent en six familles :

  1. Injection et XSS : un texte externe rendu comme du HTML exécutable suffit à détourner une session.
  2. Fuite de secrets : tout jeton accessible au JavaScript (stockage local, journaux, URL) finira par être lu par du code malveillant.
  3. Contrôle d’accès incomplet : authentifié ne veut pas dire autorisé ; sans vérification au plus près de la ressource, un utilisateur lit ou modifie ce qui ne lui appartient pas.
  4. Code serveur exposé : Server Actions et Server Components sont des points d’entrée publics, et les données passées aux composants client sont envoyées au navigateur.
  5. CORS et en-têtes permissifs : autoriser n’importe quelle origine à lire vos réponses privées revient à ouvrir vos données.
  6. Chaîne de dépendances et framework : bibliothèques compromises, obsolètes ou non corrigées. Nous y consacrons un article : npm install et sécurité.

Interface : rendre l’injection inoffensive

React échappe le texte inséré dans le JSX : c’est une bonne protection par défaut. Elle disparaît dans trois cas : dangerouslySetInnerHTML, les URL construites à partir de données externes (un href qui commence par javascript:), et le code qui manipule le DOM directement.

Si vous devez afficher du HTML (éditeur riche, contenu d’un CMS), nettoyez-le avant l’affichage avec une bibliothèque éprouvée comme DOMPurify :

import DOMPurify from 'dompurify';

export function RichText({ html }: { html: string }) {
  const clean = DOMPurify.sanitize(html, { USE_PROFILES: { html: true } });
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

Pour les liens, n’acceptez que les protocoles attendus (https:, mailto:) et refusez le reste.

Ajoutez ensuite une Content-Security-Policy : elle n’autorise que vos sources de scripts, de styles et d’images, bloque les scripts inline non signés (utilisez un nonce quand le framework le permet) et interdit l’intégration de vos pages dans des iframes non autorisées (frame-ancestors). Complétez avec Referrer-Policy et Permissions-Policy.

Enfin, gardez une interface sobre : pas d’identifiants sensibles dans l’URL, pas de traces d’erreur en production, des messages d’erreur utiles mais sans détails techniques.

Sessions : authentifier sans exposer de secret

L’application React ne doit pas manipuler de secrets. Préférez des cookies de session marqués HttpOnly, Secure et SameSite : ils ne sont pas lisibles par le JavaScript, ne voyagent qu’en HTTPS et limitent les requêtes intersites.

Si vous utilisez un SSO (OpenID Connect), faites l’échange de code côté serveur. Pour un cloisonnement plus strict, le modèle Backend-for-Frontend (BFF) place un petit serveur sur le même domaine que l’application : c’est lui qui détient les jetons et parle à l’API et au fournisseur d’identité. Résultat : aucun jeton dans le navigateur et un CORS simplifié.

À la déconnexion, invalidez la session côté serveur avant de nettoyer le client, pour éviter les sessions « fantômes ».

React Server Components et Server Actions : la nouvelle surface d’attaque

Avec React 19 et les frameworks qui l’embarquent (Next.js avec l’App Router, React Router, Waku…), une partie du code React s’exécute sur le serveur. Deux mécanismes demandent une attention particulière.

Les Server Components s’exécutent sur le serveur et envoient au navigateur le résultat du rendu, ainsi que les props des composants client qu’ils contiennent. Tout ce que vous passez en props à un composant client est donc sérialisé et visible dans le navigateur.

Les Server Actions (React parle de Server Functions) sont des fonctions serveur appelables depuis le client. Chacune devient un point d’entrée HTTP. N’importe qui peut l’appeler avec les paramètres de son choix, même si aucun bouton ne l’expose.

Ce que décembre 2025 a montré

Le 3 décembre 2025, l’équipe React a publié la vulnérabilité CVE-2025-55182, surnommée React2Shell : une exécution de code à distance sans authentification dans le protocole des Server Components, notée 10 sur 10 (CVSS). Elle touchait les versions 19.0.0 à 19.2.0 des paquets react-server-dom-webpack, react-server-dom-parcel et react-server-dom-turbopack, et donc Next.js 15 et 16 avec l’App Router. Une application était exposée dès qu’elle supportait les Server Components, même sans Server Action déclarée. La faille a été exploitée activement dans les jours suivants.

Le 11 décembre, deux vulnérabilités liées ont suivi : un déni de service (CVE-2025-55184) et une exposition du code source des Server Functions (CVE-2025-55183). Les versions à jour sont 19.0.3, 19.1.4 et 19.2.3 ou ultérieures. Le rythme n’a pas faibli : le 22 septembre 2026, Next.js a encore publié une mise à jour de sécurité hors calendrier (16.3.6) pour une faille d’exécution de code dans une dépendance.

Plus tôt, en mars 2025, la faille CVE-2025-29927 permettait de contourner le middleware de Next.js avec un simple en-tête HTTP. Les applications qui ne vérifiaient les droits que dans le middleware étaient ouvertes.

Les règles que nous appliquons

  1. Traiter chaque Server Action comme une route d’API publique : authentification, contrôle des droits sur la ressource et validation des paramètres dans la fonction elle-même.
  2. Ne jamais s’appuyer uniquement sur le middleware pour l’autorisation : il filtre, il ne protège pas les données.
  3. Centraliser l’accès aux données dans une couche serveur (Data Access Layer) qui vérifie les droits et ne renvoie que les champs utiles.
  4. Ne passer aux composants client que des objets minimaux (DTO), jamais l’enregistrement complet de la base.
  5. Marquer les modules serveur avec import 'server-only' : le build échoue si un composant client les importe.
  6. Garder les secrets hors du bundle : avec Next.js, seules les variables préfixées NEXT_PUBLIC_ sont envoyées au navigateur ; aucun secret ne doit porter ce préfixe.
  7. Mettre à jour vite : suivre les avis de sécurité de React et du framework, et appliquer les correctifs en jours, pas en mois.

Exemple simplifié de Server Action avec Next.js et Zod (auth et db sont les modules de votre application) :

'use server';

import { z } from 'zod';
import { auth } from '@/lib/auth';
import { db } from '@/lib/db';

const Input = z.object({
  invoiceId: z.string().uuid(),
  note: z.string().min(1).max(500),
});

export async function addInvoiceNote(raw: unknown) {
  const session = await auth();
  if (!session) throw new Error('Non authentifié');

  const { invoiceId, note } = Input.parse(raw);

  const invoice = await db.invoice.findUnique({ where: { id: invoiceId } });
  if (!invoice || invoice.companyId !== session.user.companyId) {
    throw new Error('Accès refusé');
  }

  await db.invoiceNote.create({
    data: { invoiceId, note, authorId: session.user.id },
  });
}

Et pour empêcher qu’un module d’accès aux données soit importé côté client :

// lib/invoices.ts
import 'server-only';

export async function getInvoiceSummary(id: string, companyId: string) {
  // vérifie les droits et ne renvoie que les champs affichés
}

API et infrastructure : droits précis, CORS serré, hygiène continue

Tout ce qui compte se vérifie côté serveur. La validation dans le formulaire React sert l’expérience utilisateur ; la validation qui protège est celle du serveur (types, tailles, formats), à chaque requête.

Réglez CORS en liste blanche : origines précises, méthodes et en-têtes explicites, credentials seulement si nécessaire, et jamais avec *. Ajoutez une limitation de débit, des quotas, des délais d’expiration et une taille maximale d’envoi de fichiers. Activez HSTS, retirez les bannières serveur, maîtrisez le cache des réponses privées et bannissez les données sensibles des journaux (un identifiant de corrélation suffit pour suivre un incident).

Un parcours en trois étapes

Étape 1 : poser les protections par défaut. Nettoyage du HTML et CSP, cookies HttpOnly, contrôle d’accès dans chaque route et chaque Server Action, CORS en liste blanche, limitation de débit, versions de React et du framework à jour.

Étape 2 : mesurer et compléter. Suivez les violations CSP, les refus d’accès, les pics de requêtes rejetées, les erreurs serveur. Ajustez au réel : resserrer une règle CORS, réduire une limite d’envoi, nettoyer un journal trop bavard.

Étape 3 : entretenir. Mises à jour de dépendances automatisées (Dependabot, Renovate) avec revue humaine, veille sur les avis de sécurité React et Next.js, revue régulière des politiques (CSP, CORS, journaux), tests des parcours sensibles et de la déconnexion.

Signaux d’alerte à traiter sans délai

  • Une version de React, de react-server-dom-* ou de Next.js antérieure aux correctifs de décembre 2025.
  • Une Server Action sans vérification de session ni de droits.
  • Des droits vérifiés uniquement dans le middleware.
  • Un objet complet de la base passé en props à un composant client.
  • Un secret dans une variable NEXT_PUBLIC_ ou dans le bundle.
  • Un jeton dans le localStorage ou dans l’URL.
  • CORS qui accepte * ou des origines larges avec des cookies.
  • dangerouslySetInnerHTML sans nettoyage en amont.

Comment nous travaillons

Sécuriser une application React n’a rien de mystérieux : neutraliser l’injection, garder les secrets hors du navigateur, vérifier les droits côté serveur à chaque point d’entrée, et maintenir le framework à jour. La difficulté est de le faire partout, de la même façon, et dans la durée.

Sur nos projets React et Next.js, c’est le rôle du lead technique Etixio : il fixe ces règles, les vérifie en revue de code et s’assure qu’elles sont testées en intégration continue. C’est ainsi que nous travaillons, par exemple, sur le front-end React d’un logiciel SaaS de conformité réglementaire.

Vous voulez un regard extérieur sur une application existante ? Nous pouvons commencer par un audit technique ou l’inscrire dans un programme cybersécurité et conformité. Présentez-nous votre projet.

FAQ

React protège-t-il nativement contre les failles XSS ?

En partie. React échappe le texte inséré dans le JSX, ce qui neutralise la plupart des injections. La protection disparaît avec dangerouslySetInnerHTML, avec des URL construites à partir de données externes (liens, redirections) ou avec du code qui manipule le DOM directement. Dans ces cas, il faut nettoyer la donnée et ajouter une Content-Security-Policy.

Où stocker un jeton d’authentification dans une application React ?

De préférence nulle part côté JavaScript. Une session dans un cookie HttpOnly, Secure et SameSite n’est pas lisible par un script injecté. Pour aller plus loin, un Backend-for-Frontend (BFF) garde les jetons côté serveur et le navigateur ne voit jamais le jeton d’accès brut.

Les Server Actions sont-elles sécurisées par défaut ?

Non. Une Server Action (ou Server Function) est un point d’entrée HTTP que n’importe qui peut appeler, même si aucun bouton de l’interface ne l’expose. Il faut authentifier l’utilisateur, vérifier ses droits sur la ressource et valider les paramètres dans la fonction elle-même, comme pour une route d’API.

Qu’est-ce que la faille React2Shell ?

C’est la vulnérabilité CVE-2025-55182, publiée le 3 décembre 2025 : une exécution de code à distance, sans authentification, dans le protocole des React Server Components (score CVSS 10). Elle concernait les versions 19.0 à 19.2.0 des paquets react-server-dom-* et les frameworks qui les embarquent, dont Next.js avec l’App Router. Elle a été exploitée activement. La seule parade était la mise à jour.

Une application React sans serveur est-elle concernée par les failles des Server Components ?

Non. Une application React purement côté client (par exemple construite avec Vite et servie en fichiers statiques) n’utilise pas le protocole des Server Components. Ses risques principaux restent l’injection, la gestion des sessions, l’API qu’elle appelle et ses dépendances.

Pour poursuivre

À lire aussi

Avançons ensemble

Quel est votre prochain projet ?

Parlons de vos enjeux pour définir le bon accompagnement.

Que recherchez-vous ?