Utilisation du « Credentials Provider » de NextAuth avec la stratégie de session basée sur la base de données

Javascript - NextJS

Next-Auth, c’est quoi ?

NextAuth.js est une solution d'authentification open-source complète pour les applications Next.js.

Elle est conçue dès le départ pour prendre en charge Next.js et les environnements Serverless.

Implémentation de Credentials Provider et OAuth avec Google Provider

Récemment, j’ai implémenté l'authentification OAuth avec Google via NextAuth sur un projet.

J’utilisais une authentification basée sur les sessions (stateful auth), un classique.

Ensuite, j’ai ajouté l’authentification par Credentials... mais ça ne fonctionnait pas.

Même en précisant la configuration suivante :

session: {
 strategy: "database",
}

Le problème ? Par défaut, l’authentification Credentials fonctionne uniquement avec JWT.

Donc, si on veut stocker les sessions en base de données, il faut gérer cette logique soi-même.

Solution : Réécrire le callback JWT

Pour que NextAuth stocke la session en base de données avec l'authentification Credentials,

il faut réécrire le comportement du callback jwt.

Voici comment j’ai résolu le problème :

import NextAuth, { NextAuthConfig, Session } from "next-auth";
import { PrismaAdapter } from "@auth/prisma-adapter";
import Google from "next-auth/providers/google";
import Credentials from "next-auth/providers/credentials";
import { prisma } from "../../prisma/prisma";
import { comparePassword } from "@/helpers/comparePassword";
import { v4 as uuidv4 } from "uuid";
import { encode as defaultEncode } from "next-auth/jwt";

const adapter = PrismaAdapter(prisma);

export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter,
  session: {
    strategy: "database",
  },

  providers: [
    Google,
    Credentials({
      credentials: {
        email: { label: "Email", type: "text", placeholder: "email@site.com" },
        password: {
          label: "Mot de passe",
          type: "password",
          placeholder: "password",
        },
      },
      async authorize(credentials) {
        if (!credentials.email || !credentials.password) return null;
 
        const userFound = await prisma.user.findFirst({
          where: {
            email: credentials.email,
          },
        });

         if (!userFound) return null;

        const passwordIsGood = await comparePassword(
          credentials?.password as string,
          userFound?.password as string
        );
 
        if (!passwordIsGood) return null;

        return userFound;
      },
    }),
  ],

  callbacks: {
    async session({ session }) {
      if (session.user?.email) {
        const subscription= await prisma.userSubscription.findFirst({
          select: {
            subscription: true,
          },
          where: {
            user: {
              email: session.user.email,
            },
          },
        });

        return {
          ...session,
          user: {
            ...session.user,
             ...subscription,
          },
        } as Session;
      }

      return session as Session;
    },
    async jwt({ account, token, user }) {
      if (account?.provider === "credentials") {
        token.credentials === true;
         const subscription = await prisma.userSubscription.findFirst({
          select: {
            subscription: true,
          },
          where: {
            user: {
              email: token?.email || "",
            },
          },
        });

        return { ...token, ...subscription, credentials: true };

      }
      return token;
    },
  },

  jwt: {
    encode: async function (params) {
      const sessionToken = uuidv4();
      if (params?.token?.credentials) {
        if (!params.token?.sub) {
          throw new Error("No user id found in token");
        }

        const createSession = await adapter?.createSession?.({
          sessionToken,
          userId: params.token?.sub,
          expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), //30 jours
        });
 
        if (!createSession) {
          throw new Error("Failed to create session");
        }
        return sessionToken;
      }
      return defaultEncode(params);
    },
  },

  pages: {
    signIn: "login",
  },

} satisfies NextAuthConfig);

Si vous avez une meilleure solution, n’hésitez pas à partager !

Ressources utiles :

https://www.youtube.com/watch?v=rZ-WNsxu17s

https://github.com/nextauthjs/next-auth/discussions/4394#discussioncomment-3293618

https://nneko.branche.online/next-auth-credentials-provider-with-the-database-session-strategy/

Publié le 24/02/2025