Comment sécuriser ton application Next.js afin d'éviter les problèmes...
Sérieux, tu n'as pas le choix de le faire.
Pour respecter tes utilisateurs et éviter les problèmes 👿
Voici comment faire :
Ne jamais faire confiance aux données que tu reçois
TypeScript est uniquement actif dans ton IDE, au runtime (quand l'utilisateur va sur ton application), TypeScript n'existe plus.
Il faut donc valider les données !
Par exemple ce code :
"use server";
const updatePost = async (post: Post) => {
await prisma.post.create({ data: post });
};
Tu penses que c'est sécurisé cette action du serveur car tu as mis le type Post
?
PAS DU TOUT !
À tout moment, l'utilisateur peut envoyer ceci :
{
title: "Test post",
markdown: "Test post",
createdAt: new Date(),
id: "1",
updatedAt: new Date(),
user: {
create: {
image: "https://avatars....",
plan: "PRO",
email: "monemail@gmail.com",
},
},
}
Ici, il passe la clé "user", ce qui lui permet de créer un nouvel utilisateur avec un plan premium.
Ne jamais faire confiance aux données que tu reçois
Pour cela, tu vas toujours valider les données, par exemple avec Zod, en créant un schéma qui limite les données en entrée.
Authentification
Quand on appelle ton backend, tu dois toujours te demander :
▪ A-t-il le droit de faire cette action ?
Par exemple, pour la suppression d'un post.
Tu ne peux pas simplement créer une route API qui prend un ID et supprime cet ID.
Tu dois vérifier que l'utilisateur est le bon.
Si on prend cet exemple :
export const deletePostAction = async (postId: string) => {
const user = await auth();
if (!user) {
throw new Error("You must be connected to delete a post");
}
const deletedPost = await prisma.post.delete({
where: {
id: postId,
},
});
return { post: deletedPost };
};
On a beau vérifier que l'utilisateur est connecté, on ne vérifie pas que le post lui appartient.
Pour ça, il faudrait rajouter ceci :
export const deletePostAction = async (postId: string) => {
const user = await auth();
if (!user) {
throw new Error("You must be connected to delete a post");
}
const deletedPost = await prisma.post.delete({
where: {
id: postId,
userId: user.id,
},
});
return { post: deletedPost };
};
Maintenant, quand on supprime le post, on vérifie que l'utilisateur est bien le propriétaire du post.
Si ce n'est pas le cas, la commande delete
va renvoyer une erreur.
Autorisation
La 3ème forme de sécurité est l'autorisation.
C'est-à-dire vérifier que l'utilisateur a le droit de faire cette action, via ses permissions ou une donnée en base de données.
Pour notre exemple précédent, on pourrait imaginer qu'un admin a le droit de supprimer tous les posts, même ceux qui ne lui appartiennent pas.
On pourrait donc rajouter ceci :
export const deletePostAction = async (postId: string) => {
const user = await auth();
if (!user) {
throw new Error("You must be connected to delete a post");
}
const postToDelete = await prisma.post.findUnique({
where: {
id: postId,
},
});
if (user.id !== postToDelete.userId && user.role !== "ADMIN") {
throw new Error("You can only delete your own posts");
}
the deletedPost = await prisma.post.delete({
where: {
id: postId,
},
});
return { post: deletedPost };
};
De cette manière, on vérifie que l'utilisateur est bien le propriétaire du post ou qu'il est admin.
Conclusion
Chaque API Routes, Server Actions que tu crées en NextJS doit toujours être sécurisé.
Pour ça, pose-toi toujours ces questions :
- Les données envoyées par l'utilisateur sont-elles vérifiées ?
- L'utilisateur a-t-il le droit de faire cette action ?
- L'utilisateur a-t-il la permission de faire cette action ?
Si tu veux avoir des dizaines d'outils pour t'aider à créer une authentification et une autorisation parfaites.
Je te conseille de faire un tour ici :
Récupère ton accès NOW.TS, la boilerplate Next.js pour créer des applications de PRO peu importe ton niveau.
Profite d'un cours complet afin de créer des applications SaaS rapidement et efficacement.