écrits/blog/2026/07
Blog6 juil. 2026·6 min

Sécuriser les Server Actions Next.js : Bloquer les appels non authentifiés en 2026

Les Server Actions de Next.js exposent des endpoints HTTP POST appelables par défaut. Découvrez comment ajouter une authentification, un middleware et des outils d'audit pour protéger votre application.

Les Server Actions de Next.js sont l'une des fonctionnalités les plus puissantes du framework — elles permettent d'écrire du code côté serveur directement dans les composants React, en créant automatiquement des endpoints POST. Mais cette commodité cache un risque de sécurité qui peut exposer des données sensibles et permettre des prises de contrôle de comptes.

Le problème : les Server Actions sont des endpoints POST publics

Quand vous définissez une Server Action, Next.js crée un endpoint POST que n'importe quel client peut appeler — pas seulement votre interface. Cela signifie qu'un utilisateur non authentifié peut appeler directement vos actions de mutation via curl ou fetch, contourner complètement les gardes d'authentification côté client, et accéder ou modifier des données auxquelles il ne devrait pas avoir accès.

// Cette server action est accessible à n'importe qui
'use server'
 
export async function deletePost(postId: string) {
  await db.posts.delete({ where: { id: postId } }) // Pas de vérification auth !
}

L'URL de l'endpoint est dérivée du hash de l'action, mais elle est publiquement accessible à n'importe quel client HTTP — pas seulement votre frontend.

Pourquoi cela se produit

Next.js n'ajoute pas d'authentification par défaut aux Server Actions. Les développeurs supposent souvent que parce que l'action est "côté serveur", elle est protégée — mais n'importe quel client HTTP peut appeler ces endpoints directement. Le framework les traite comme des fonctions serveur tout en les exposant comme des routes accessibles via le réseau.

Solution 1 : Vérifications d'authentification inline

La protection la plus directe est de vérifier l'authentification au début de chaque action :

'use server'
 
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
 
export async function deletePost(postId: string) {
  const session = await getServerSession(authOptions)
 
  if (!session?.user) {
    throw new Error('Non autorisé')
  }
 
  // Vérifier également la propriété
  const post = await db.posts.findUnique({ where: { id: postId } })
  if (post?.authorId !== session.user.id) {
    throw new Error('Interdit')
  }
 
  await db.posts.delete({ where: { id: postId } })
}

C'est explicite mais répétitif sur de nombreuses actions. Un utilitaire partagé gère cela mieux à grande échelle.

Solution 2 : Wrapper d'authentification

Créez un wrapper withAuth qui impose l'authentification pour toute action :

// lib/with-auth.ts
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
 
type ActionFn<T, R> = (user: User, input: T) => Promise<R>
 
export async function withAuth<T, R>(
  action: ActionFn<T, R>,
  input: T
): Promise<R> {
  const session = await getServerSession(authOptions)
 
  if (!session?.user) {
    throw new Error("Non autorisé : veuillez vous connecter pour continuer.")
  }
 
  return action(session.user, input)
}

L'utilisation devient propre et cohérente :

'use server'
 
import { withAuth } from '@/lib/with-auth'
 
export async function deletePost(postId: string) {
  return withAuth(async (user) => {
    const post = await db.posts.findUnique({ where: { id: postId } })
    if (post?.authorId !== user.id) throw new Error('Interdit')
    return db.posts.delete({ where: { id: postId } })
  }, postId)
}

Solution 3 : Protection par Middleware

Le Middleware Next.js fournit une couche de protection centralisée pour des segments de routes entiers :

// middleware.ts
import { withAuth } from 'next-auth/middleware'
 
export default withAuth({
  pages: {
    signIn: '/auth/signin',
  },
})
 
export const config = {
  matcher: ['/dashboard/:path*'],
}

Le Middleware est excellent pour les portes au niveau des routes, mais ne remplace pas les vérifications d'authentification par action pour les opérations sensibles, car il s'exécute avant l'action et ne peut pas imposer des permissions au niveau des objets.

Considérations CSRF

Next.js 14 et versions ultérieures incluent une protection CSRF intégrée pour les Server Actions via la validation du header Origin. Cependant, cette protection ne s'applique qu'aux appels de même origine et ne remplace pas l'authentification. Pour les applications utilisant des sessions basées sur des cookies, ajouter des tokens CSRF explicites aux mutations sensibles fournit une couche de défense supplémentaire.

Auditer votre application avec Shipcheck

L'outil @shipcheck/cli analyse votre projet Next.js pour trouver des Server Actions non protégées :

npx @shipcheck/cli

Il identifie les fichiers de Server Actions qui manquent de vérifications d'authentification et produit un rapport des vulnérabilités potentielles. L'exécuter dans votre checklist de pré-déploiement détecte les lacunes avant qu'elles n'atteignent la production.

Limitation du débit pour les actions sensibles

L'authentification seule ne suffit pas pour les actions à fort enjeu. Ajoutez une limitation du débit pour prévenir les attaques par force brute ou les abus :

'use server'
 
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
 
const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(5, '1 m'),
})
 
export async function requestPasswordReset(email: string) {
  const session = await getServerSession(authOptions)
  const identifier = session?.user?.id ?? email
 
  const result = await ratelimit.limit(identifier)
  if (!result.success) {
    throw new Error('Trop de requêtes. Veuillez réessayer plus tard.')
  }
 
  // Continuer avec la logique de réinitialisation
}

Checklist de sécurité pour la production

Avant de déployer toute application Next.js avec des Server Actions :

  • Chaque action qui lit ou modifie des données effectue une vérification d'authentification
  • Une autorisation au niveau des objets garantit que les utilisateurs n'affectent que leurs propres ressources
  • La limitation du débit est appliquée aux actions sensibles
  • Les messages d'erreur ne révèlent pas de détails internes
  • Les tentatives d'authentification échouées sont enregistrées pour la surveillance
  • @shipcheck/cli a été exécuté et les résultats ont été traités

La bonne approche

Traitez les Server Actions de la même manière que les routes d'API REST — parce que c'est exactement ce qu'elles sont au niveau réseau. L'expérience développeur consistant à co-localiser la logique serveur avec vos composants est genuinement puissante, mais elle ne rend pas ce code moins exposé à l'internet public.

Une authentification explicite au niveau de l'action, combinée au Middleware pour les portes au niveau des routes et à la limitation du débit pour les opérations à haut risque, vous donne une défense en profondeur qui protège vos utilisateurs quelle que soit la manière dont un attaquant tente d'accéder à votre serveur.

Le principe est le même qui a sécurisé les APIs REST pendant des années : ne jamais faire confiance à une requête, toujours vérifier l'appelant, et toujours limiter les permissions à ce que l'utilisateur authentifié est réellement autorisé à faire.