Comment sécuriser les cookies de session pour Strapi et Next.js
Apprenez à configurer HttpOnly, Secure et SameSite, à faire pivoter les refresh tokens et à vous protéger contre les attaques CSRF avec Strapi 5 et Next.js.
Par l’équipe Revisionary · · 4 min de lecture
Strapi 5 et Next.js peuvent partager un flux d’authentification sécurisé lorsqu’on configure correctement les cookies de session et qu’on impose la rotation des tokens. En définissant les attributs HttpOnly, Secure et le bon SameSite, en stockant les tokens d’accès à courte durée en mémoire, et en utilisant des refresh tokens rotatifs, vous protégez les utilisateurs contre les attaques XSS, CSRF et le détournement de session.
1. Pourquoi les paramètres des cookies importent
- HttpOnly : empêche le JavaScript d’accéder au cookie, limitant le vol par XSS.
- Secure : le cookie n’est envoyé qu’en HTTPS, évitant les interceptions en mode man‑in‑the‑middle.
- SameSite : contrôle le moment où le navigateur inclut le cookie, bloquant la plupart des attaques CSRF.
- Expiration courte : réduit la fenêtre d’attaque par relecture.
Si l’un de ces indicateurs manque, un attaquant capable d’injecter du script ou de piéger un utilisateur sur un site malveillant pourra récupérer ou exploiter la session.
2. Flux d’authentification recommandé
- L’utilisateur se connecte → Strapi valide les identifiants et délivre un JWT d’accès signé (15‑30 minutes) ainsi qu’un refresh token (7 jours).
- Le token d’accès est stocké dans une variable mémoire côté client (jamais dans le localStorage ni dans les cookies).
- Le refresh token est stocké dans un cookie Secure, HttpOnly, SameSite=Strict.
- Lorsque le token d’accès expire, le client envoie une requête vers
/auth/refreshavec le cookie de rafraîchissement ; Strapi le valide, fait pivoter le refresh token et renvoie un nouveau token d’accès. - Lors de la déconnexion, le cookie de rafraîchissement est supprimé et le serveur révoque le token.
Ce schéma suit les recommandations OWASP pour l’authentification basée sur des tokens et évite les écueils des cookies à longue durée.
3. Configurer Strapi pour définir un cookie de refresh‑token sécurisé
Le plugin d’authentification intégré de Strapi peut être étendu avec un contrôleur personnalisé qui émet le cookie de rafraîchissement. Dans src/api/auth/controllers/custom.js :
// src/api/auth/controllers/custom.js
import jwt from 'jsonwebtoken';
import { getService } from '@strapi/utils';
export default {
async login(ctx) {
const { identifier, password } = ctx.request.body;
const user = await getService('user').validateUser(identifier, password);
if (!user) {
return ctx.badRequest('Invalid credentials');
}
// Token d’accès – courte durée, renvoyé dans le corps de la réponse
const accessToken = jwt.sign({ id: user.id }, process.env.JWT_SECRET, { expiresIn: '15m' });
// Refresh token – longue durée, stocké dans un cookie HttpOnly
const refreshToken = jwt.sign({ id: user.id }, process.env.JWT_REFRESH_SECRET, { expiresIn: '7d' });
ctx.cookies.set('refreshToken', refreshToken, {
httpOnly: true,
secure: true, // nécessite HTTPS
sameSite: 'strict',
path: '/',
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 jours en ms
});
ctx.send({ accessToken });
},
async refresh(ctx) {
const token = ctx.cookies.get('refreshToken');
if (!token) return ctx.unauthorized('No refresh token');
try {
const payload = jwt.verify(token, process.env.JWT_REFRESH_SECRET);
// Rotation du token
const newRefresh = jwt.sign({ id: payload.id }, process.env.JWT_REFRESH_SECRET, { expiresIn: '7d' });
ctx.cookies.set('refreshToken', newRefresh, {
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/',
maxAge: 7 * 24 * 60 * 60 * 1000,
});
const newAccess = jwt.sign({ id: payload.id }, process.env.JWT_SECRET, { expiresIn: '15m' });
ctx.send({ accessToken: newAccess });
} catch (err) {
return ctx.unauthorized('Invalid refresh token');
}
},
async logout(ctx) {
ctx.cookies.set('refreshToken', null, { httpOnly: true, secure: true, sameSite: 'strict', path: '/', maxAge: 0 });
ctx.send({ message: 'Logged out' });
},
};Points clés
httpOnly: truebloque l’accès côté client.secure: trueforce le HTTPS ; en développement local vous pouvez le conditionner.sameSite: 'strict'empêche l’envoi du cookie lors de requêtes inter‑sites.- Le refresh token est rotatif à chaque utilisation, limitant les relectures.
3.1 Enregistrement des routes personnalisées
Ajoutez les routes dans src/api/auth/routes/custom.js :
// src/api/auth/routes/custom.js
module.exports = {
routes: [
{ method: 'POST', path: '/login', handler: 'custom.login' },
{ method: 'POST', path: '/refresh', handler: 'custom.refresh' },
{ method: 'POST', path: '/logout', handler: 'custom.logout' },
],
};Redémarrez Strapi, les points d’accès sont alors disponibles.
4. Implémentation côté client avec Next.js
Next.js peut appeler le point d’entrée de connexion, stocker le token d’accès dans un contexte React, et laisser le navigateur envoyer automatiquement le cookie de rafraîchissement.
// lib/auth.tsx
import { createContext, useContext, useState, ReactNode } from 'react';
import axios from 'axios';
interface AuthContextProps {
accessToken: string | null;
login: (email: string, password: string) => Promise<void>;
logout: () => Promise<void>;
}
const AuthContext = createContext<AuthContextProps | undefined>(undefined);
export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [accessToken, setAccessToken] = useState<string | null>(null);
const login = async (email: string, password: string) => {
const res = await axios.post('/api/auth/login', { identifier: email, password });
setAccessToken(res.data.accessToken);
};
const logout = async () => {
await axios.post('/api/auth/logout');
setAccessToken(null);
};
// Rafraîchissement automatique du token
const refresh = async () => {
try {
const res = await axios.post('/api/auth/refresh');
setAccessToken(res.data.accessToken);
} catch {
setAccessToken(null);
}
};
// Rafraîchir le token toutes les 14 minutes (juste avant l’expiration du token d’accès)
setInterval(refresh, 14 * 60 * 1000);
return (
<AuthContext.Provider value={{ accessToken, login, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
return ctx;
};Pourquoi ne pas stocker le token dans un cookie ? Le placer dans un cookie l’exposerait aux attaques XSS. Le conserver en mémoire signifie qu’un rechargement de page le supprime, forçant le flux de rafraîchissement protégé par le cookie HttpOnly.
4.1 Ajout d’une protection CSRF (optionnel)
Si vous devez accepter des requêtes modifiant l’état qui ne sont pas couvertes par le flux JWT (par exemple des formulaires), activez le middleware CSRF de Strapi et transmettez le token dans un en‑tête :
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/api/:path*',
headers: [{ key: 'X-CSRF-Token', value: process.env.CSRF_TOKEN }],
},
];
},
};Côté serveur, ajoutez la configuration du plugin csrf intégré dans config/plugins.js :
module.exports = ({ env }) => ({
csrf: {
enabled: true,
// Le token est envoyé dans un cookie nommé `csrf-token`
cookie: { httpOnly: true, sameSite: 'strict', secure: true },
},
});Le client peut lire le cookie csrf-token (il n’est pas HttpOnly) et le placer dans l’en‑tête X-CSRF-Token pour les requêtes POST/PUT/DELETE.
5. Renforcement du serveur Strapi
Au‑delà des drapeaux du cookie, appliquez les paramètres défensifs suivants :
| Paramètre | Recommandation | Où le configurer |
|---|---|---|
| Rate limiting | Limiter les tentatives de connexion (ex. 5 par minute par IP) | Middleware personnalisé ou plugin tel que strapi-plugin-rate-limit |
| Hashage des mots de passe | Utiliser Argon2 ou bcrypt avec un facteur de coût ≥12 | Par défaut dans Strapi, vérifiez la version de bcrypt |
| Journal d’audit | Enregistrer les connexions réussies/échouées, les révocations de token | /docs/recover-and-audit/audit – active la traçabilité |
| Confidentialité | Anonymiser les adresses IP dans les journaux d’audit | /docs/settings/privacy |
| Permissions des types de contenu | Restreindre le rôle public aux seules actions find sur les points d’accès sûrs | Admin UI → Roles & Permissions |
Activer le journal d’audit vous fournit une trace infalsifiable des événements d’authentification, utile pour la réponse aux incidents et la conformité RGPD.
6. Tester votre configuration
- Inspecter les cookies – Dans Chrome DevTools, vérifiez que le cookie
refreshTokenpossède les attributsHttpOnly,SecureetSameSite=Strict. - Tentative XSS – Essayez de lire
document.cookiedepuis une page contenant du script injecté ; le cookie de rafraîchissement doit rester invisible. - Test CSRF – Depuis une autre origine, envoyez une requête POST sans l’en‑tête CSRF ; Strapi doit répondre
403. - Rotation des tokens – Après un appel à
/refresh, assurez‑vous que l’ancien refresh token n’est plus accepté (vous pouvez le vérifier avec Postman).
7. Déploiement en production
- Assurez‑vous que votre load‑balancer termine le TLS avant de transmettre les requêtes à Strapi, tout en conservant le drapeau
securedu cookie. - Utilisez des variables d’environnement pour les clés secrètes (
JWT_SECRET,JWT_REFRESH_SECRET). - Définissez
NODE_ENV=productionafin que Strapi active les sécurités par défaut. - Faites tourner régulièrement les clés secrètes et invalidez les refresh tokens existants via un point d’accès administrateur.
8. Quand envisager Revisionary
Si vous avez besoin d’un enregistrement immuable des modifications liées à l’authentification (modifications de permissions, révocations de tokens), le journal d’audit Revisionary consigne ces actions aux côtés des éditions de contenu. Il offre un historique consultable pouvant soutenir les audits de conformité sans développement supplémentaire.
9. Checklist – sessions sécurisées pour Strapi + Next.js
- Appliquer
HttpOnly,Secure,SameSite=Strictau cookie de refresh‑token. - Conserver les access tokens en mémoire, jamais dans le localStorage ou les cookies.
- Faire pivoter les refresh tokens à chaque utilisation.
- Imposer une courte durée de vie aux access tokens (≤30 minutes).
- Activer la protection CSRF pour les points d’accès modifiant l’état.
- Ajouter un rate limiting sur les endpoints de connexion et de rafraîchissement.
- Enregistrer les événements d’authentification dans le journal d’audit.
- Anonymiser les IP dans les journaux d’audit pour la conformité vie privée.
- Tester les drapeaux des cookies et la révocation des tokens après le déploiement.
Suivre ce guide vous procure une stratégie de session robuste, conforme aux recommandations OWASP, pour les applications headless CMS bâties avec Strapi et Next.js.
L’équipe Revisionary
Les développeurs de Revisionary, un plugin d’historique des versions pour Strapi 5. Nous écrivons sur Strapi, le développement web, l’IA et la sécurité des applications.
Cet article a été rédigé avec l'aide d'une IA.