Changer le thème

Authentification et sécurité API Next.js : guide complet du JWT au rate limiting

Easton editorial illustration: cache waterfall instrument

Le téléphone vibre — alerte de facture du cloud provider : 7 800 dollars.

Je frotte les yeux, je crois mal lire. Le mois dernier : 120 dollars. Je consulte le détail : 18 millions d’appels API. Mon projet perso tourne habituellement à quelques centaines d’appels par jour.

Un crawler avait trouvé mon endpoint API sans aucune protection et avait bombardé pendant trois jours. Ce jour-là, j’ai compris pourquoi on dit que la sécurité API n’est pas optionnelle.

Cet article rassemble les pièges que j’ai rencontrés et les solutions étudiées. De l’authentification JWT à la configuration CORS, du rate limiting à la validation des entrées — pas de théorie creuse, mais du code prêt à l’emploi.

Pourquoi la sécurité API est si importante

Menaces courantes sur les API

10.0
Score CVSS
Vulnérabilité critique React Server Components (CVE-2025-55182) en décembre 2025 : exécution de code arbitraire possible

En décembre dernier, React a publié un avis de sécurité critique, CVE-2025-55182, score CVSS au maximum — 10.0.

Qu’est-ce que ça signifie ? Le score le plus élevé. Un attaquant peut exécuter du code arbitraire sur votre serveur avec une requête HTTP spécialement construite. Si vous utilisez React Server Components sans mise à jour, vous êtes exposé.

Ce n’est pas un cas isolé. En mars, une faille de contournement d’autorisation (CVE-2025-29927), score 9.1. L’attaquant forge un en-tête de requête et contourne votre middleware d’authentification. Vous pensiez être protégé ? Il passe directement.

Au-delà de ces vulnérabilités critiques, les menaces quotidiennes sont plus nombreuses :

Crawlers malveillants et attaques DDoS. Sans rate limiting, une API peut s’effondrer en minutes. J’ai vu des endpoints de login recevoir 3 000 requêtes par seconde — brute force, serveur à l’arrêt.

Fuites de données. Sans contrôle d’accès, l’utilisateur A voit les commandes de l’utilisateur B. Une fois dans les médias, la réputation de la marque est ruinée.

Injections. SQL injection, XSS, injection de commandes… ça sonne dépassé, mais des milliers de projets en souffrent chaque année. Vous pensez « React échappe automatiquement », mais sans validation côté API, ça ne suffit pas.

Spécificités des API Routes Next.js

Les API Routes Next.js diffèrent d’un backend traditionnel :

Serverless first. Sur Vercel, chaque requête API est une fonction Serverless indépendante. Avantage : scaling automatique. Inconvénient : sans état — pas de Session en mémoire, il faut JWT ou Session en base.

Mélangé avec le frontend. Tout dans un même dépôt ; les variables d’environnement peuvent fuiter côté client. J’ai vu quelqu’un mettre DATABASE_URL dans .env et l’exposer dans le bundle frontend sur GitHub.

Limites de l’edge computing. Avec Edge Runtime, certaines API Node.js sont indisponibles ; bibliothèques crypto et connexions DB à repenser. Les solutions de sécurité suivent.

En bref, l’API Next.js est votre backend — plus léger, plus flexible, et plus facile à mal configurer.

Authentification API en pratique

Guide de choix du schéma d’authentification

Question concrète : JWT ou Session ?

Au début de mes projets Next.js, j’hésitais aussi. JWT « moderne obligatoire » d’un côté, Session « plus sûr » de l’autre. En pratique, ça dépend du contexte.

JWT convient quand :

  • Déploiement multi-serveurs (Serverless, edge)
  • Authentification cross-domain (frontend sur app.com, API sur api.com)
  • Pas envie de gérer le stockage de Session — simplicité

JWT encode les infos utilisateur dans un token ; le serveur ne stocke rien, chaque requête apporte le token. Scaling horizontal très simple.

Session convient quand :

  • Contrôle actif côté serveur (révoquer une session, changement de permissions en temps réel)
  • Sécurité maximale, aucune info utilisateur côté client
  • Redis ou base déjà en place pour gérer les Sessions

La Session vit côté serveur ; le client n’a qu’un Session ID. Pour invalider un utilisateur, supprimez la Session — impossible avec JWT.

Mon critère : petits projets perso → JWT, simple ; enterprise avec contrôle fin → Session. Ne vous bloquez pas, commencez avec l’un, changez si besoin.

Implémentation complète JWT

Supposons que vous choisissez JWT. Comment l’implémenter dans Next.js ?

Étape 1 : générer et vérifier le Token

Installez une bibliothèque :

npm install jose

Pourquoi pas jsonwebtoken ? Pas compatible Edge Runtime. jose suit les standards Web, fonctionne partout.

Créez lib/auth.ts :

import { SignJWT, jwtVerify } from 'jose';

const secret = new TextEncoder().encode(
  process.env.JWT_SECRET || 'your-secret-key-at-least-32-characters'
);

export async function createToken(payload: { userId: string }) {
  return new SignJWT(payload)
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setExpirationTime('15m') // expire après 15 minutes
    .sign(secret);
}

export async function verifyToken(token: string) {
  try {
    const { payload } = await jwtVerify(token, secret);
    return payload;
  } catch {
    return null;
  }
}

Notez l’expiration 15m. Beaucoup mettent 7 ou 30 jours — pratique, mais si le token fuite, l’attaquant l’utilise longtemps. Access Token court + Refresh Token long : la bonne approche.

Étape 2 : stocker le Token — pas localStorage

Point crucial. Beaucoup de tutos mettent le token dans localStorage ; une attaque XSS vole tout.

Bonne pratique : cookie HttpOnly.

À la connexion :

// app/api/login/route.ts
import { NextResponse } from 'next/server';
import { createToken } from '@/lib/auth';

export async function POST(request: Request) {
  // Vérifier identifiants...

  const token = await createToken({ userId: user.id });

  const response = NextResponse.json({ success: true });
  response.cookies.set('token', token, {
    httpOnly: true,    // illisible en JS, anti-XSS
    secure: true,      // HTTPS uniquement
    sameSite: 'lax',   // anti-CSRF
    maxAge: 900,       // 15 min, aligné sur l'expiration du token
  });

  return response;
}

HttpOnly est la clé : JavaScript ne lit pas ce cookie, XSS ne peut pas le voler.

Étape 3 : middleware pour protéger l’API

Token en place — comment exiger la connexion sur certaines API ? Middleware Next.js.

Créez middleware.ts :

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { verifyToken } from './lib/auth';

export async function middleware(request: NextRequest) {
  const token = request.cookies.get('token')?.value;

  if (!token) {
    return NextResponse.json(
      { error: 'Unauthorized' },
      { status: 401 }
    );
  }

  const payload = await verifyToken(token);
  if (!payload) {
    return NextResponse.json(
      { error: 'Invalid token' },
      { status: 401 }
    );
  }

  // Validé : transmettre l'utilisateur à l'API
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-user-id', payload.userId as string);

  return NextResponse.next({
    request: {
      headers: requestHeaders,
    },
  });
}

export const config = {
  matcher: '/api/protected/:path*',
};

Toutes les routes /api/protected/* sont protégées. Dans l’API, récupérez l’utilisateur :

// app/api/protected/profile/route.ts
import { headers } from 'next/headers';

export async function GET() {
  const headersList = await headers();
  const userId = headersList.get('x-user-id');

  // Charger l'utilisateur depuis la base...
}

Étape 4 : mécanisme de rafraîchissement du Token

15 minutes, l’utilisateur se reconnecte sans cesse ? Introduisez le Refresh Token.

Access Token court (15 min), Refresh Token long (30 jours). Access Token expiré → échangez avec Refresh Token, pas de nouvelle connexion.

L’implémentation est un peu plus complexe ; next-auth intègre cette logique.

Intégration rapide avec NextAuth.js

Franchement, tout coder soi-même aide à comprendre, mais c’est lourd. Pour démarrer vite : NextAuth.js (maintenant Auth.js).

Points forts : sécurité par défaut prête à l’emploi :

  • Protection CSRF automatique
  • Signature et chiffrement de Session
  • JWT et Session en base
  • OAuth Google, GitHub, etc.

Installation :

npm install next-auth

Créez app/api/auth/[...nextauth]/route.ts :

import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';

const handler = NextAuth({
  providers: [
    CredentialsProvider({
      name: 'Credentials',
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" }
      },
      async authorize(credentials) {
        // Vérifier identifiants...
        if (user) {
          return { id: user.id, email: user.email };
        }
        return null;
      }
    })
  ],
  session: {
    strategy: 'jwt',  // JWT, adapté au Serverless
    maxAge: 30 * 24 * 60 * 60, // 30 jours
  },
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.userId = user.id;
      }
      return token;
    },
    async session({ session, token }) {
      session.userId = token.userId;
      return session;
    }
  }
});

export { handler as GET, handler as POST };

Puis vérifiez la session dans l’API :

import { getServerSession } from 'next-auth';

export async function GET() {
  const session = await getServerSession();

  if (!session) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // Session OK, traiter la requête...
}

Simple. NextAuth gère tokens et rafraîchissement de Session.

Configuration CORS détaillée

Essence du CORS et problèmes courants

CORS (Cross-Origin Resource Sharing) embête beaucoup de monde. En dev tout va, en prod :

Access to fetch at 'https://api.example.com' from origin 'https://app.example.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.

En bref, le navigateur interdit à la page A d’accéder aux ressources de B. Sur app.com, vous appelez api.com : le navigateur demande à api.com « app.com est autorisé ? » L’API doit répondre oui.

Pourquoi pas d’erreur en dev ?

Next.js en dev : frontend et API sur localhost:3000, même origine. En prod, frontend sur Vercel, API ailleurs → cross-domain.

C’est quoi une requête Preflight ?

Un POST avec en-têtes custom (ex. Authorization) déclenche d’abord une OPTIONS. C’est le Preflight. Sans handler OPTIONS → 404 → échec CORS.

J’ai déjà oublié OPTIONS : POST OK, CORS en échec, longue recherche.

Trois méthodes pour configurer CORS dans Next.js

Méthode 1 : next.config.js global

Quand toutes les API partagent les mêmes origines autorisées.

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/api/:path*',
        headers: [
          { key: 'Access-Control-Allow-Origin', value: 'https://app.example.com' },
          { key: 'Access-Control-Allow-Methods', value: 'GET,POST,PUT,DELETE' },
          { key: 'Access-Control-Allow-Headers', value: 'Content-Type, Authorization' },
        ],
      },
    ];
  },
};

Avantage : une fois pour toutes. Inconvénient : peu flexible par route.

Méthode 2 : Middleware

Pour jugement dynamique et traitement unifié.

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Requête Preflight
  if (request.method === 'OPTIONS') {
    return new NextResponse(null, {
      status: 200,
      headers: {
        'Access-Control-Allow-Origin': 'https://app.example.com',
        'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
        'Access-Control-Allow-Headers': 'Content-Type, Authorization',
      },
    });
  }

  // Requête normale : en-têtes CORS
  const response = NextResponse.next();
  response.headers.set('Access-Control-Allow-Origin', 'https://app.example.com');

  return response;
}

export const config = {
  matcher: '/api/:path*',
};

Accès à l’objet request pour autoriser selon l’origine.

Méthode 3 : dans la API Route

Pour une API avec des règles spécifiques.

// app/api/public/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  const data = { message: 'Hello' };

  return NextResponse.json(data, {
    headers: {
      'Access-Control-Allow-Origin': '*', // API publique, toutes origines
    },
  });
}

export async function OPTIONS() {
  return new NextResponse(null, {
    status: 200,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET',
      'Access-Control-Allow-Headers': 'Content-Type',
    },
  });
}

Chaque API doit gérer OPTIONS, sinon Preflight échoue.

Bonnes pratiques CORS

1. N’abusez pas du wildcard

Beaucoup écrivent :

'Access-Control-Allow-Origin': '*'

Tout site peut appeler votre API. OK pour données publiques ; pour infos utilisateur ou actions sensibles, c’est une exposition totale.

Bonne pratique : domaines explicites.

const allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];

const origin = request.headers.get('origin');
if (origin && allowedOrigins.includes(origin)) {
  response.headers.set('Access-Control-Allow-Origin', origin);
}

2. Attention aux credentials

Si l’API lit les cookies (Session), côté frontend :

fetch('https://api.example.com', {
  credentials: 'include',
});

Côté backend :

response.headers.set('Access-Control-Allow-Credentials', 'true');

Mais Access-Control-Allow-Origin ne peut pas être * — le navigateur refuse. Domaine précis obligatoire.

3. Gérer Preflight

Requêtes avec Authorization ou Content-Type: application/json déclenchent souvent Preflight. Répondez à OPTIONS.

Fonction utilitaire :

export function corsHeaders(origin?: string) {
  return {
    'Access-Control-Allow-Origin': origin || 'https://app.example.com',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    'Access-Control-Max-Age': '86400', // cache Preflight 24 h
  };
}

Réutilisez dans chaque API.

Rate limiting API

Pourquoi le rate limiting

Retour à l’histoire du début. 18 millions d’appels — avec un plafond de 100 req/min/IP, la perte aurait été de quelques dizaines de dollars, pas 7 800.

Le rate limiting limite le nombre d’appels API par unité de temps. Simple et efficace :

Anti-DDoS. Trop de requêtes ? Au-delà du seuil, refus — le serveur tient.

Anti brute force. Login sans limite : 10 000 mots de passe/seconde. Avec 5 tentatives/min/IP, la difficulté explose.

Protection des ressources. DB et APIs tierces coûtent cher. Le rate limiting évite qu’un utilisateur épuise tout.

Comparaison des solutions

Option 1 : @upstash/ratelimit + Vercel KV

Ma solution habituelle. Upstash = Redis Serverless, partenaire Vercel, intégration simple.

Avantages :

  • Compatible Serverless, pas de serveur Redis à gérer
  • Algorithmes : fenêtre fixe, glissante, token bucket
  • Quota gratuit suffisant pour projets perso

Inconvénients :

  • Payant à gros volume
  • Dépendance tierce

Option 2 : Redis self-hosted

Si vous avez déjà Redis ou refusez le tiers.

Avantages :

  • Contrôle total, pas de surcoût
  • Logique custom possible

Inconvénients :

  • Maintenance Redis
  • Config complexe en Serverless

Option 3 : rate limiting en mémoire

Sans Redis, limite simple en mémoire.

Avantages :

  • Zéro dépendance, quelques lignes
  • Dev et petits projets

Inconvénients :

  • Serverless : instances différentes, mémoire non partagée → limite inefficace
  • Redémarrage = données perdues

Conseil : perso/Serverless → Upstash ; enterprise avec serveurs → Redis ; demo/local → mémoire.

Exemple de code

Avec Upstash, pas à pas.

Étape 1 : installation et config

npm install @upstash/ratelimit @upstash/redis

Créez une base Redis sur Upstash, mettez UPSTASH_REDIS_REST_URL et UPSTASH_REDIS_REST_TOKEN dans .env :

UPSTASH_REDIS_REST_URL=https://xxx.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token

Étape 2 : créer le limiteur

Créez lib/rate-limit.ts :

import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

// Client Redis
const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL!,
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});

// Limiteur : fenêtre glissante, max 10 req / 10 s
export const ratelimit = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(10, '10 s'),
  analytics: true,
});

slidingWindow(10, '10 s') : max 10 requêtes en 10 s. Fenêtre glissante plus lisse que fixe.

Étape 3 : utiliser dans l’API

// app/api/protected/route.ts
import { NextResponse } from 'next/server';
import { ratelimit } from '@/lib/rate-limit';

export async function GET(request: Request) {
  // IP client
  const ip = request.headers.get('x-forwarded-for') || 'unknown';

  // Vérifier le rate limit
  const { success, limit, remaining, reset } = await ratelimit.limit(ip);

  if (!success) {
    return NextResponse.json(
      {
        error: 'Too many requests',
        limit,
        remaining,
        reset: new Date(reset),
      },
      {
        status: 429,
        headers: {
          'X-RateLimit-Limit': limit.toString(),
          'X-RateLimit-Remaining': remaining.toString(),
          'X-RateLimit-Reset': reset.toString(),
        },
      }
    );
  }

  // Sous la limite, traiter normalement
  return NextResponse.json({ data: 'Success' });
}

IP comme identifiant. Avec login, utilisez userId :

const identifier = session?.userId || ip;
const { success } = await ratelimit.limit(identifier);

Utilisateur connecté → par user ; anonyme → par IP.

Étape 4 : rate limiting global en Middleware

// middleware.ts
import { ratelimit } from '@/lib/rate-limit';

export async function middleware(request: NextRequest) {
  const ip = request.ip || 'unknown';
  const { success } = await ratelimit.limit(ip);

  if (!success) {
    return NextResponse.json(
      { error: 'Too many requests' },
      { status: 429 }
    );
  }

  return NextResponse.next();
}

export const config = {
  matcher: '/api/:path*',
};

Toutes les API protégées d’un coup.

Validation des entrées et défense

Pourquoi la validation est la première ligne

« Ne jamais faire confiance aux entrées utilisateur » — règle d’or de la sécurité.

Validation frontend ? Inutile seule. DevTools, code modifié, validation contournée. La vraie défense est côté serveur.

Injection SQL. '; DROP TABLE users; -- concaténé dans du SQL → catastrophe. ORM partout, mais SQL natif existe encore.

XSS. <script>alert('hacked')</script> stocké → exécution chez d’autres, cookies volés. React échappe, sauf avec dangerouslySetInnerHTML.

DoS. JSON de 10 Mo → mémoire Serverless saturée. Chaîne énorme → regex qui ne finit jamais.

La validation bloque la plupart des attaques basiques. Sans elle, le reste est une passoire.

Validation type-safe avec Zod

Zod est ma bibliothèque préférée. TypeScript natif, parfait avec le système de types.

npm install zod

Usage de base

Définissez un schema :

import { z } from 'zod';

const userSchema = z.object({
  email: z.string().email('Invalid email'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
  age: z.number().int().min(18).max(120),
});

Dans l’API :

// app/api/register/route.ts
import { NextResponse } from 'next/server';
import { userSchema } from '@/lib/schemas';

export async function POST(request: Request) {
  const body = await request.json();

  // Valider les données
  const result = userSchema.safeParse(body);

  if (!result.success) {
    return NextResponse.json(
      {
        error: 'Validation failed',
        details: result.error.format(),
      },
      { status: 400 }
    );
  }

  // Validé : données typées
  const { email, password, age } = result.data;

  // Suite du traitement...
}

Préférez safeParse (pas d’exception). parse lève une erreur.

Pourquoi mieux que la validation manuelle ?

Manuel :

if (!body.email || typeof body.email !== 'string') {
  return error;
}
if (!body.email.includes('@')) {
  return error;
}
// des dizaines de lignes...

Zod :

z.string().email()

Une ligne, types inférés.

Schéma complet de validation

Au-delà des types : logique métier et cas limites.

1. Valider le body

const postSchema = z.object({
  title: z.string().min(1).max(100),
  content: z.string().max(10000), // limite de taille
  tags: z.array(z.string()).max(10), // limite du tableau
  publishedAt: z.string().datetime().optional(),
});

2. Valider les query params

// app/api/posts/route.ts
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);

  const querySchema = z.object({
    page: z.coerce.number().int().min(1).default(1),
    limit: z.coerce.number().int().min(1).max(100).default(20),
    sort: z.enum(['asc', 'desc']).default('desc'),
  });

  const params = querySchema.parse({
    page: searchParams.get('page'),
    limit: searchParams.get('limit'),
    sort: searchParams.get('sort'),
  });

  // params.page est un number typé
}

z.coerce.number() convertit les strings en nombres.

3. Règles custom

const passwordSchema = z.string()
  .min(8)
  .refine((val) => /[A-Z]/.test(val), 'Must contain uppercase')
  .refine((val) => /[a-z]/.test(val), 'Must contain lowercase')
  .refine((val) => /[0-9]/.test(val), 'Must contain number');

Validation async :

const emailSchema = z.string().email().refine(
  async (email) => {
    const exists = await checkEmailExists(email);
    return !exists;
  },
  'Email already taken'
);

4. Gestion des erreurs

if (!result.success) {
  const errors = result.error.errors.map(err => ({
    field: err.path.join('.'),
    message: err.message,
  }));

  return NextResponse.json({ errors }, { status: 400 });
}

Erreurs structurées, faciles à afficher côté frontend.

Autres mesures de sécurité

1. Protection CSRF

Les Server Actions Next.js comparent Origin et Host. API Routes : à faire vous-même. NextAuth le gère ; sinon token CSRF :

// Token CSRF en cookie, envoyé par le frontend, vérifié côté serveur

2. Content Security Policy (CSP)

Dans next.config.js, limitez les ressources chargées :

{
  headers: [
    {
      key: 'Content-Security-Policy',
      value: "default-src 'self'; script-src 'self'; style-src 'self';",
    },
  ],
}

Même avec XSS, scripts malveillants bloqués.

3. Sécurité des variables d’environnement

Next.js distingue :

  • NEXT_PUBLIC_* → exposé au frontend
  • sans préfixe → serveur uniquement

Ne mettez jamais de secrets dans NEXT_PUBLIC_. J’ai vu NEXT_PUBLIC_API_KEY en prod.

4. Protection injection SQL

Prisma, Drizzle : requêtes paramétrées automatiques.

SQL natif :

// ❌ dangereux
db.query(`SELECT * FROM users WHERE id = ${userId}`);

// ✅ sûr
db.query('SELECT * FROM users WHERE id = ?', [userId]);

5. Mettre à jour les dépendances

npm audit
npm update

La faille React de février : mise à jour = corrigé. Ne remettez pas à plus tard.

Checklist sécurité complète

Authentification

  • ✅ Token dans cookie HttpOnly, pas localStorage
  • ✅ Access Token ≤ 30 minutes
  • ✅ Mécanisme Refresh Token
  • ✅ Clé JWT ≥ 32 caractères, en variable d’environnement
  • ✅ Cookie secure et sameSite
  • ✅ Middleware sur API sensibles

CORS

  • ✅ Pas de Access-Control-Allow-Origin: * sur API sensibles
  • ✅ Liste de domaines autorisés explicite
  • ✅ OPTIONS Preflight géré
  • Access-Control-Allow-Credentials: true si credentials
  • ✅ Vérifier CORS en production

Rate limiting

  • ✅ Limite stricte sur login, register, reset password
  • ✅ Stratégies différentes utilisateur authentifié / anonyme
  • ✅ 429 et en-tête Retry-After
  • ✅ Redis ou Upstash (pas mémoire seule en Serverless)
  • ✅ Surveiller les déclenchements, ajuster les seuils

Validation des entrées

  • ✅ Toutes les entrées validées côté serveur
  • ✅ Zod ou équivalent type-safe
  • ✅ Limites de taille string, array, object
  • ✅ Formats (email, URL, date)
  • ✅ Messages d’erreur clairs

Audit régulier

  • npm audit mensuel, corriger les critiques
  • ✅ Next.js et React à jour
  • ✅ Suivre les avis de sécurité Next.js
  • ✅ Revue des variables d’environnement
  • ✅ Code review auth et permissions

Imprimez la checklist : avant un nouveau projet, avant chaque déploiement.

Conclusion

En une phrase : la sécurité API est un système, pas un réglage unique.

Auth, CORS, rate limiting, validation — ça prend du temps à configurer. Après une fuite, un serveur down ou une facture astronomique, le coût de rattrapage est bien plus élevé.

Mon expérience : mise en place initiale, une demi-journée à une journée. Ensuite, copier-coller et adapter — nouveau projet en une quinzaine de minutes. Surtout : dormir tranquille.

Actions :

  1. Auditez vos projets avec la checklist
  2. Commencez par l’essentiel : auth et rate limiting
  3. Suivez l’actualité sécurité : blog Next.js, GitHub Security Advisories
  4. Partagez avec l’équipe — la sécurité concerne tout le monde

Dernière remarque : la faille React CVSS 10.0 de décembre 2025 touche énormément de projets. Pas encore à jour ? Mettez à jour maintenant. Les correctifs de sécurité ne attendent pas.

Le chemin de la sécurité API est long, chaque pas compte. J’espère que cet article vous évitera quelques pièges.

FAQ

Faut-il utiliser JWT ou Session pour les API Next.js ?
Cela dépend du contexte. JWT convient aux déploiements Serverless et à l'authentification cross-domain (sans état, facile à scaler) ; Session convient quand le serveur doit contrôler activement (révoquer une session) ou pour des exigences de sécurité très élevées. JWT pour les projets personnels, Session pour les applications enterprise.
Pourquoi ne pas stocker le JWT token dans localStorage ?
localStorage est accessible en JavaScript : en cas d'attaque XSS, le token peut être volé. Utilisez un cookie HttpOnly, illisible par JavaScript — même avec une faille XSS, le token reste protégé.
Pourquoi pas d'erreur CORS en dev mais oui en production ?
En développement, frontend et API sont sur localhost:3000 (même origine, pas de cross-domain). En production, frontend et API peuvent être sur des domaines différents : le navigateur applique CORS. Solution : configurer correctement l'en-tête Access-Control-Allow-Origin côté API.
Comment implémenter le rate limiting en environnement Serverless ?
Recommandation : @upstash/ratelimit + Vercel KV. Chaque requête Serverless peut être une nouvelle instance (mémoire non partagée) — le throttling en mémoire échoue. Upstash Redis persiste les données de rate limit partagées entre instances, parfait pour Serverless.
Comment empêcher le brute force des mots de passe sur l'API ?
Protection multicouche : 1) rate limiting strict sur login (ex. 5 tentatives/min/IP) ; 2) validation de la force du mot de passe avec Zod ; 3) verrouillage de compte (5 échecs → blocage 30 min) ; 4) CAPTCHA pour augmenter le coût d'attaque.

15 min de lecture · Publié le: 5 janv. 2026 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog