codelynx.dev
🇫🇷🇬🇧

Back 10/09/2023

How to Setup NextAuth Password with Database (Next Page Directory)

Written by Melvyn Malherbe on 10/09/2023


If you go to the NextAuth Credentials section, you will see that the functionality is "discouraged" to limit its usage.

Also, you'll need to set up the JWT strategy to make it work.

But... how do we use it with our database? What if we don't want to change to JWT and want flexibility?

I'll help you set it up with your database in this post.

Setup

To set up the NextAuth Credentials, we will follow the basic steps! In your NextAuth config, add the Credentials Provider:

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

export const authOptions: (
  req?: NextApiRequest,
  res?: NextApiResponse
) => AuthOptions = (req, res) => ({
  // ...
  providers: [
    CredentialsProvider({
      name: 'Credentials',
      credentials: {
        email: { label: 'Email', type: 'text', placeholder: 'your email' },
        password: { label: 'Password', type: 'password' },
      },
      async authorize(credentials, req) {
        // your logic with your database here
      }
    });
  ],
});

Note something important: I use an arrow function that takes req and res to have access to the req and res of the API route.

To make it work, you will need to use it in the pages/api/auth/[...nextauth].ts file like this:

TS
import { NextApiRequest, NextApiResponse } from 'next';
import NextAuth from 'next-auth';
import { authOptions } from '~/lib/nextauth';

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  return NextAuth(req, res, authOptions(req, res));
}

Because to handle the database authentication, we will need the req and the res of the API route.

Authorize function

To create our password strategy, we will need to add a callback function to our config.

You will need to add the signIn function like this:

TS
export const authOptions: (
  req?: NextApiRequest,
  res?: NextApiResponse
) => AuthOptions = (req, res) => ({
  // ...
  callbacks: {
    async signIn({ user, account, profile, email, credentials }) {
      // your logic here
    },
  },
});

In this function, we will start by knowing if the signIn method was called with the credentials options, because we won't handle any other case.

TS
export const authOptions: (
  req?: NextApiRequest,
  res?: NextApiResponse
) => AuthOptions = (req, res) => ({
  // ...
  callbacks: {
    async signIn({ user, account, profile, email, credentials }) {
      if (!req || !res) return true;

      if (
        !req.query.nextauth?.includes('callback') ||
        !req.query.nextauth?.includes('credentials') ||
        req.method !== 'POST' ||
        !user
      ) {
        return true;
      }
    },
  },
});

We check if there is no callback OR if there is no credentials in the query OR if the method is not POST, we return true to let NextAuth handle the authentication.

If there is no user, it means that the user is not found in the database, so we return true to let NextAuth handle the authentication.

TS
export const authOptions: (
  req?: NextApiRequest,
  res?: NextApiResponse
) => AuthOptions = (req, res) => ({
  // ...
  callbacks: {
    async signIn({ user, account, profile, email, credentials }) {
      if (!req || !res) return true;
      if (
        !req.query.nextauth?.includes('callback') ||
        !req.query.nextauth?.includes('credentials') ||
        req.method !== 'POST'
      ) {
        return true;
      }
    },
  },
});

And now, we will need to do exactly like NextAuth does with other methods to handle authentication with a password.

First, at the start of my file, I had this:

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

The tokenName in the Cookie has a different name if we are in development or production. So we will need to do the same thing here.

TS
export const authOptions: (
  req?: NextApiRequest,
  res?: NextApiResponse
) => AuthOptions = (req, res) => ({
  // ...
  callbacks: {
    async signIn({ user, account, profile, email, credentials }) {
      if (!req || !res) return true;
      if (
        !req.query.nextauth?.includes('callback') ||
        !req.query.nextauth?.includes('credentials') ||
        req.method !== 'POST'
      ) {
        return true;
      }

      const uuid = uuidv4();

      // 7 days
      const expireAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);

      // Create a session with YOUR database, for the example, I use Prisma
      await prisma.session.create({
        data: {
          sessionToken: uuid,
          userId: user.id,
          expires: expireAt,
        },
      });

      // Create a cookie
      const cookie = new Cookie(req, res, {
        // ⚠️ Important to use secure in production or it won't work
        secure: process.env.NODE_ENV === 'production',
      });

      cookie.set(tokenName, uuid, {
        expires: expireAt,
        secure: env.NODE_ENV === 'production',
        sameSite: 'lax',
        path: '/',
      });

      return false;
    },
  },
});

In this code, I generate a uuid that will act as a token. I use the library uuid to do that.

Then I generate an expireAt date that represents 7 days after today, and I create a session in

prisma using the sessionToken as uuid, the userId, and the expireAt date.

Then I create a cookie with the Cookie library, and I set the tokenName as the uuid, and I set the expireAt date.

Next, I need to add a Cookie to the res object, so I use the library Cookie to do that. Don't forget to add the secure option to true if you are in production.

I finally set the cookie with the tokenName as the uuid, the expireAt date, the secure option, the sameSite option, and the path option.

With all of this, your authentication will work in NextJS 12 and NextJS 13.

Finish

I hope this article will help you make your authentication work with NextJS 12 and NextJS 13.