DEV Community

Rahul Solanki
Rahul Solanki

Posted on • Originally published at rahulj9ablogs.hashnode.dev on

Step-by-Step Guide: Implementing NextAuth.js Authentication in Next.js 13 App Directory

Hey everyone! In this article, we are going to set up Authentication for our web app with the help of Next-Auth With all of that will also show you to set up Prisma, react-query and other techs to ensure smooth and better authentication.

The article will include:-

  1. Installation and folder structure

  2. Setting up Prisma and Environment variable

  3. Securing passwords with the help of bcrypt

  4. API and hooks.

  5. Register, Login and check credentials

  6. Caching and fetching current Users with react-query

Installation:-

First of all, let's install all the dependencies-

 npm i next-auth


npm install -D prisma


npm install @prisma/client

Enter fullscreen mode Exit fullscreen mode

and now can install the rest of the dependencies later.

Setting up prisma:-

As we have already installed Prisma and Prisma client, now all we to initialize the Prisma in our project. To do so run

npx prisma init

Enter fullscreen mode Exit fullscreen mode

After running this command you should see a file in your codebase something like this-

And this is the file where we have to set up our Prisma schema. next, you need to have your database URL; you can choose any database like MongoDB, MySQL or Postgresql. It's totally up to you. I am going to use Mysql for my project. After having your database URL, change the provider to the database you choose and paste the URL into the ".env" file in your root directory.

(Note: Check if your ".env" file is included in ".gitignore" or not, if not then include it)

After having all of this let's write our User model-

You can set up fields according to your application. Like I was building an e-commerce so I setup accordingly. To keep things unique like the id, username or email, you can use the 'unique' keyword. and to expect things in a specific type you can use 'String' or 'number' as I did in the case of String or id. In the case of id 'default(uuid())' will ensure to generate a random id everytime; Remember that there might be some changes in using 'default(uuid())'.

and after setting up everthing all we have to do is to push the model into our database with the command-

npx prisma db push

Enter fullscreen mode Exit fullscreen mode

Now our Prisma model is setup, now we need to create a new library that can provide out Prisma client to global like this:-

This library will ensure that next.js doesn't create a bunch of clients on every hot reload.

Setting up Next-Auth:-

Now let's set up our Next-auth, all we have to do is to create out next auth file with this file structure-

Now before setting up our 'authoptions' you need to install bcrypt

npm install bcrypt
npm install -D @types/bcrypt

Enter fullscreen mode Exit fullscreen mode

and prisma adapter for our next auth

npm install @next-auth/prisma-adapter

Enter fullscreen mode Exit fullscreen mode

and in our config folder you can create a file named - 'nextauth.config.tsx'

while signing-in we will pass the credentials, and the above code will check if we have the right credentials or not. The code will first accept the right form of credentials and then check if the credentials have email and password or not, and next it's going to check if the user's email exist in database or not and 'bcrypt.compare' function will check if the password is right or not.

after creating the above file, create the variables-

and set the values whatever you want.

Now let's build our register endpoint-

Register API endpoint-

we will use bcrypt to hash our password. To know more about bcrypt you can read my another article on how to keep you user's password-

https://dev.to/rahulj9a/how-to-keep-users-password-safe-in-database-g0f

And this endpoint will create a new user in out database.

Get the user after signin-

Now let's create a another lib. that will check if the user is sign in or not and if yes then will fetch the user's data.

The code will fetch the session details and extract the email to check if the user is registered or not and if yes then will fetch the user's data for further use.

But to access the session we have to provide the session to the main layout file like this-


//app>layout.tsx
import {ReactQueryProvider} from "@/providers/reactQueryProvider";
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>

          <NextAuthProvider>
            {children}
          </NextAuthProvider>

      </body>
    </html>
  );
}

Enter fullscreen mode Exit fullscreen mode

Now we can't use this liberary directly and we need an API endpoint to fetch the current user's detail

API end point to fetch user's detail

As we have fetch the user's data threw the serverAuth lib. all we have to do is build an api to facilitate our frontend.

Now we have build API to fetch currentUser's detail. But we don't want to make API calls again and again throughout the app. So lets's cache the User's detail using react-query

Caching our current User-

install react-query

npm i react-query

Enter fullscreen mode Exit fullscreen mode

and build a hook can be accessed throughout the app. So to do that we have to provide the query client to the main layout file and that can be done like this-


import { NextAuthProvider } from "@/providers/nextAuthProvider";
import {ReactQueryProvider} from "@/providers/reactQueryProvider";
 export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <ReactQueryProvider>
          <NextAuthProvider>
             {children}
          </NextAuthProvider>
        </ReactQueryProvider>
      </body>
    </html>
  );
}

Enter fullscreen mode Exit fullscreen mode

and now the useCurrentUser hook-

The hook will cache the details of User and provide that to the frontend while keep checking if the user's details have changed or not and if it is changed then will fetch again in the background.

Final result-

Register Modal-

import { signIn } from "next-auth/react";
import { useCallback, useState } from "react";
import Input from "../Input";
import Modal from "../Modal";
import axios from "axios";

const RegisterModal = () => {
     const [email, setemail] = useState("");
     const [password, setpassword] = useState("");
     const [name, setname] = useState("");
     const [username, setuserName] = useState("");
     const [loading, setloading] = useState(false);

     const onSubmit = useCallback(async () => {
          try {
               setloading(true);

               //add Register and login

               await axios.post("/api/register", {
                    email,
                    password,
                    username,
                    name,
               });

               signIn("credentials", { email, password });

          } catch (error) {
               console.log(error);

          } finally {
               setloading(false);
          }
     }, [email, password, username, name]);

     const bodyContent = (
          <div className="flex flex-col gap-4">
               <Input
                    placeholder="Email"
                    onChange={(e) => setemail(e.target.value)}
                    value={email}
                    disabled={loading}
               />
               <Input
                    placeholder="Name"
                    onChange={(e) => setname(e.target.value)}
                    value={name}
                    disabled={loading}
               />
               <Input
                    placeholder="UserName"
                    onChange={(e) => setuserName(e.target.value)}
                    value={username}
                    disabled={loading}
               />
               <Input
                    placeholder="Password"
                    type="password"
                    onChange={(e) => setpassword(e.target.value)}
                    value={password}
                    disabled={loading}
               />
          </div>
     );

     return (
          <div>
               <Modal
                    disabled={loading}

                    title="Create an account"
                    actionLabel="Regsiter"

                    onSubmit={onSubmit}
                    body={bodyContent}

               />
          </div>
     );
};

export default RegisterModal;

Enter fullscreen mode Exit fullscreen mode

Login Modal-


import { useCallback, useState } from "react";
import Input from "../Input";
import Modal from "../Modal";

import { signIn } from "next-auth/react";

const LoginModal = () => {

     const [email, setemail] = useState("");
     const [password, setpassword] = useState("");
     const [loading, setloading] = useState(false);

     const onSubmit = useCallback(async () => {
          try {
               setloading(true);
               await signIn("credentials", {
                    email,
                    password,
               });

          } catch (error) {
               console.log(error);
          } finally {
               setloading(false);
          }
     }, [email, password]);

     const bodyContent = (
          <div className="flex flex-col gap-4">
               <Input
                    placeholder="Email"
                    onChange={(e) => setemail(e.target.value)}
                    value={email}
                    disabled={loading}
               />
               <Input
                    placeholder="Password"
                    type="password"
                    onChange={(e) => setpassword(e.target.value)}
                    value={password}
                    disabled={loading}
               />
          </div>
     );

     return (
          <div>
               <Modal
                    disabled={loading}

                    title="Login"
                    actionLabel="Sign in"

                    onSubmit={onSubmit}
                    body={bodyContent}

               />
          </div>
     );
};

export default LoginModal;

Enter fullscreen mode Exit fullscreen mode

and whenever we need currentUser details-

import usecurrentUser from "@/hooks/useCurrentUser";
export default function User(){
const {data:currentUser,isLoading,isError} = usecurrentUser()
const email = currentUser.email
}

Enter fullscreen mode Exit fullscreen mode

and yes we have build our authentication. Ya! I know its a bit complicated and it will create pain just for 1 or 2 times as it's not that much hard of you try to understand what happening.

Let me know your suggestion. I am also on Twitter where I used to post my journey and knowledge. So follow me there and DM for may help.

Top comments (0)