codelynx.dev
🇫🇷🇬🇧

Retour 27/08/2024

Comment configurer NextAuth Password avec une base de données (Next App Directory)

Écris par Melvyn Malherbe le 27/08/2024


Si vous allez à la section NextAuth Credentials, vous verrez que la fonctionnalité est "découragée" afin de limiter son utilisation.

De plus, vous devrez configurer la stratégie JWT pour la faire fonctionner.

Mais... comment l'utiliser avec notre base de données ? Que faire si nous ne voulons pas passer à JWT et que nous voulons de la flexibilité ?

Je vais vous aider à le configurer avec votre base de données dans cet article.

Ce tutoriel vous aide à configurer NextAuth avec un mot de passe et la structure du répertoire NextJS App. Si vous recherchez la version du répertoire de pages, consultez mon post précédent.

Configuration

Pour configurer les Credentials de NextAuth, nous allons suivre les étapes de base ! Dans votre configuration NextAuth, ajoutez le Credentials Provider :

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

export const { handlers, auth: baseAuth } = NextAuth((request) => ({
  adapter: PrismaAdapter(prisma),
  providers: [
    CredentialsProvider({
      name: 'Credentials',
      credentials: {
        email: { label: 'Email', type: 'text', placeholder: 'votre email' },
        password: { label: 'Password', type: 'password' },
      },
      async authorize(credentials, req) {
        // votre logique avec votre base de données ici
      }
    });
  ],
  session: {
    strategy: "database",
  },
  secret: env.NEXTAUTH_SECRET,
}));

Ensuite, vous pouvez utiliser la méthode handlers dans le fichier app/api/auth/[...nextauth]/route.ts :

TS
import { handlers } from '@/lib/auth/auth';

export const { GET, POST } = handlers;

Fonction d'autorisation

Pour créer notre stratégie par mot de passe, nous devrons ajouter une fonction events à notre configuration.

Vous devrez ajouter la fonction signIn comme ceci :

TS
export const { handlers, auth: baseAuth } = NextAuth((request) => ({
  events: {
    async signIn({ user }) {
      // votre logique ici
    },
  },
});

Dans cette fonction, nous commencerons par savoir si la méthode signIn a été appelée avec les options credentials, car nous ne gérerons aucun autre cas.

TS
export const { handlers, auth: baseAuth } = NextAuth((request) => ({
  events: {
    async signIn({ user }) {
      if (!request) {
        return;
      }

      if (request.method !== "POST") {
        return;
      }

      const currentUrl = request.url;

      // Nous vérifions si l'utilisateur se connecte avec des credentials
      if (!currentUrl.includes("credentials")) {
        return;
      }

      if (!currentUrl.includes("callback")) {
        return;
      }
    },
  },
});

Nous vérifions s'il n'y a pas de callback OU s'il n'y a pas de credentials dans la query OU si la méthode n'est pas POST, nous retournons true pour laisser NextAuth gérer l'authentification.

Et maintenant, nous devrons faire exactement comme NextAuth le fait avec d'autres méthodes pour gérer l'authentification avec un mot de passe.

D'abord, au début de mon fichier, j'avais ceci :

TS
const tokenName =
  env.NODE_ENV === 'development'
    ? 'next-auth.session-token'
    : '__Secure-next-auth.session-token';

Le tokenName dans le Cookie a un nom différent si nous sommes en développement ou en production. Donc, nous devrons faire la même chose ici.

TS
export const { handlers, auth: baseAuth } = NextAuth((request) => ({
  callbacks: {
    async signIn({ user }) {
      if (!request) {
        return;
      }

      if (request.method !== "POST") {
        return;
      }

      const currentUrl = request.url;

      // Nous vérifions si l'utilisateur se connecte avec des credentials
      if (!currentUrl.includes("credentials")) {
        return;
      }

      if (!currentUrl.includes("callback")) {
        return;
      }

       // Nous créons une session
      const uuid = nanoid();
      const expireAt = addDays(new Date(), 14);
      // J'utilise prisma pour l'exemple
      await prisma.session.create({
        data: {
          sessionToken: uuid,
          userId: user.id ?? "",
          expires: expireAt,
        },
      });

      const cookieList = cookies();

      const AUTH_COOKIE_NAME =
        env.NODE_ENV === "development"
          ? "authjs.session-token"
          : "__Secure-authjs.session-token";

      // Créez et définissez un cookie avec exactement la même valeur
      cookieList.set(AUTH_COOKIE_NAME, uuid, {
        expires: expireAt,
        path: "/",
        sameSite: "lax",
        httpOnly: true,
        secure: env.NODE_ENV === "production",
      });

      return;
    },
  },
});

Dans ce code, je génère un nanoid qui agira comme un token. J'utilise la bibliothèque nanoid pour cela.

Ensuite, je génère une date expireAt qui représente 7 jours après aujourd'hui, et je crée une session dans

prisma en utilisant le sessionToken comme uuid, le userId, et la date expireAt.

Ensuite, je crée un cookie avec la bibliothèque Cookie, et je définis le tokenName comme le uuid, et je définis la date expireAt.

Ensuite, je dois ajouter un Cookie à l'objet res, donc j'utilise la bibliothèque Cookie pour cela. N'oubliez pas d'ajouter l'option secure à true si vous êtes en production.

Je définis enfin le cookie avec le tokenName comme le uuid, la date expireAt, l'option secure, l'option sameSite, et l'option path.

Override jwt

Si vous faites tout ce qui précède, cela ne fonctionnera pas. NextAuth supprimera le token car il ne le reconnaît pas.

Pour résoudre ce problème, nous désactiverons la méthode jwt :

TS
export const { handlers, auth: baseAuth } = NextAuth((request) => ({
  // ...
  jwt: {
    encode() {
      return "";
    },
    async decode() {
      return null;
    },
  };
}));

Avec ce code, NextAuth connectera correctement l'utilisateur avec le cookie créé ensemble.

Conclusion

J'espère que cet article vous aidera à faire fonctionner votre authentification avec NextJS 14.