Next.js 14 Authentication with NextAuth.js and MongoDB, featuring Custom Sign-up and Login Pages

Free


Next.js 14 Authentication with NextAuth.js and MongoDB, featuring Custom Sign-up and Login Pages


Next JS

Snapshots


Step 1: First Install following npm packages by running below command
npm install axios bcrypt mongoose next-auth


Step 2: Update next.config.mjs file
 - Add Mongo DB URL By using Mongo DB Compass and copy and paste your connection string
   and rest of parameters you can keep as example provided below

/** @type {import('next').NextConfig} */
const nextConfig = {
    env: {
        NEXT_PUBLIC_HOSTNAME: "http://localhost:3000/api/",
        MONGODB_URI: "mongodb://localhost:27017/codingmstr",
        NEXTAUTH_SECRET: "codingmstrsecret",
        NEXTAUTH_URL: "http://localhost:3000",
        SECRET: "RAMDOM_STRING",
    },
};

export default nextConfig;



Step 3: Create NextAuth Files

Go to 'src\app\api\auth\[...nextauth]' and create
options.ts
route.ts


Step 4: Update options.ts


//@ts-nocheck
import type { NextAuthOptions } from "next-auth";
import GitHubProvider from "next-auth/providers/github";
import CredentialsProvider from "next-auth/providers/credentials";
import GoogleProvider from "next-auth/providers/google";

const baseURL = process.env.NEXT_PUBLIC_HOSTNAME + "login";

export const options: NextAuthOptions = {
  session: {
    strategy: "jwt",
  },
  providers: [
    GitHubProvider({
      clientId: process.env.GITHUB_ID as string,
      clientSecret: process.env.GITHUB_SECRET as string,
    }),
    GoogleProvider({
      clientId: process.env.NEXT_GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.NEXT_GOOGLE_CLIENT_SECRET as string,
    }),
    CredentialsProvider({
      name: "Credentials",
      credentials: {
        username: { label: "Email", type: "email", placeholder: "" },
        password: { label: "Password", type: "password" },
      },
      async authorize(credentials, req) {
        // This is where you need to retrieve user data
        // to verify with credentials
        // Docs: https://next-auth.js.org/configuration/providers/credentials
        const requestBody = {
          email: credentials.email,
          password: credentials.password,
        };
        const res = await fetch(baseURL, {
          method: "POST",
          body: JSON.stringify(requestBody),
          headers: { "Content-Type": "application/json" },
        });
        const resdata = await res.json();
        console.log("Login...", resdata);
        if (
          resdata.status === 400 ||
          resdata.status === 401 ||
          resdata.status === 403 ||
          resdata.status === 500
        ) {
          return null;
        }
        if (resdata.status === 200 || resdata.status === 201) {
          return resdata;
        }
        // Return null if user data could not be retrieved
        return null;
      },
    }),
  ],
  pages: {
    // signIn: "/",
    // error: '/auth/error',
    // signOut: '/auth/signout'
  },
  callbacks: {
    async jwt({ token, user }) {
      // the user present here gets the same data as received
// from DB call  made above -> fetchUserInfo(credentials.opt)
      return { ...token, ...user };
    },
    async session({ session, user, token }) {
      // user param present in the session(function) does not recive
//all the data from DB call -> fetchUserInfo(credentials.opt)
      return token;
    },
  },
  secret: process.env.JWT_SECRET,
};



Step 5: Update route.ts File


import NextAuth from 'next-auth'
import { options } from './options'

const handler = NextAuth(options)

export { handler as GET, handler as POST }



Step 6. Go to 'src\app\context\authrovider.tsx' and create

authprovider.tsx source code

"use client";

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

export default function AuthProvider({
  children,
}: {
  children: React.ReactNode;
}) {
  return <SessionProvider>{children}</SessionProvider>;
}



Step 7. Use AuthProvider and Update root layout.tsx file
 - Path of layout.jsx file is ''src\app\layout.tsx''


import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import AuthProvider from "./context/authprovider";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <AuthProvider>{children}</AuthProvider>
      </body>
    </html>
  );
}






NOW WE WILL SET THE DATABASE
Step 8: Create dbConnect.js file at 'src\db\config\dbConnect.js' directory 


import mongoose from "mongoose";

const MONGODB_URI = process.env.MONGODB_URI;

if (!MONGODB_URI) {
    throw new Error(
        'Please define Mongo DB URI'
    )
}

let cached = global.mongoose

if (!cached) {
    cached = global.mongoose = { conn: null, promise: null }
}

async function dbConnect() {
    if (cached.conn) {
        return cached.conn
    }

    if (!cached.promise) {
        const opts = {
            bufferCommands: true,
        }

        cached.promise = await mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
            return mongoose
        })
    }

    try {
        cached.conn = await cached.promise
    } catch (e) {
        cached.promise = null
        throw e
    }

    return cached.conn
}

export default dbConnect





Step 9: Create USER MODEL file 'user.js' at 'src\db\models\user.js' directory 

import mongoose from 'mongoose';
const { Schema } = mongoose;


const userSchema = new Schema({
    name: String,
    email: String,
    password: String
});

export default mongoose.models.User || mongoose.model('User', userSchema);





Step 10: Create LOGIN API file 'route.js' at 'src\app\api\login\route.js' directory 


import dbConnect from "@/db/config/dbConnect";
import User from "@/db/models/user";
import bcrypt from 'bcrypt';

dbConnect();


export async function GET(request) {
    const users = await User.find({}).sort({ _id: -1 });

    let data = JSON.stringify(users);
    return new Response(data, {
        status: 200,
    });

}

export async function POST(request) {

    const { email, password } = await request.json();

    // Check if email and password are provided
    if (!email || !password) {
        return new Response(JSON.stringify({
            success: false,
            status: 400,
            message: 'email and password are required',
            data: email,
        }));
    }

    // Find the user in the database
    const user = await User.findOne({ email });

    // If user is not found, return an error
    if (!user) {
        return new Response(JSON.stringify({
            success: false,
            status: 400,
            message: 'Invalid credentials'
        }));
    }

    // if(password == user.password) {
    //     return new Response(JSON.stringify({
    //         success: true,
    //         status: 200,
    //         data: user
    //     }));
    // }else{
    //       return new Response(JSON.stringify({
    //         success: false,
    //         status: 400,
    //         message: 'Wrong Password'
    //     }));
    // }

    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (isPasswordValid) {
        return new Response(JSON.stringify({
            success: true,
            status: 200,
            data: user
        }));
    } else {
        return new Response(JSON.stringify({
            success: false,
            status: 400,
            message: 'Wrong Password'
        }));
    }

}






Step 11: Create REGISTER API file 'route.js' at 'src\app\api\register\route.js' directory 

import dbConnect from "@/db/config/dbConnect";
import User from "@/db/models/user";
import bcrypt from 'bcrypt';

dbConnect();

export async function POST(request) {

    const { email, password } = await request.json();

    const existingUser = await User.findOne({ email });
    if (existingUser) {
        return new Response(data, {
            message: 'Email is already taken',
            status: 400,
        });
    }

    // Hash the password before storing it in the database
    const hashedPassword = await bcrypt.hash(password, 10);
    console.log('Data ', email, '   ', password, '   ', hashedPassword);

    // If user is created successfully, return a success message
    const users = await User.create({ email, password: hashedPassword });

    let data = JSON.stringify(users);
    console.log('users ', users);
    return new Response(data, {
        status: 200,
    });

}




Step 12: Create Login Page

"use client";
import React from "react";
import { signIn } from "next-auth/react";
import { useRouter } from 'next/navigation';

const Login = () => {
  const router = useRouter();

  const submitHandler = async (data) => {
    const resdata = await signIn("credentials", {
      email: data.email,
      password: data.password,
      redirect: false,
    });

    console.log(resdata);
    if (
      resdata.status === 400 ||
      resdata.status === 401 ||
      resdata.status === 403
    ) {
      console.log("Invalid Credentials!");
      alert('Invalid Credentials!');
    } else if (resdata.status === 500) {
      console.log(
        "Server error!"
      );
      alert('Server error!');
    } else {
      alert('Login successfull!');
      router.push('/home');
      console.log(resdata);
    }
  };

  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-50">
      <div className="max-w-md w-full p-6 bg-white rounded-md shadow-md">
        <h2 className="text-2xl font-bold mb-4">Login</h2>
        <form onSubmit={(e) => {
          e.preventDefault();
          submitHandler({
            email: e.target.username.value,
            password: e.target.password.value,
          });
        }}>
          <div className="mb-4">
            <label
              htmlFor="username"
              className="block text-sm font-medium text-gray-600"
            >
              Username
            </label>
            <input
              type="text"
              id="username"
              name="username"
              className="mt-1 p-2 w-full border rounded-md"
            />
          </div>
          <div className="mb-4">
            <label
              htmlFor="password"
              className="block text-sm font-medium text-gray-600"
            >
              Password
            </label>
            <input
              type="password"
              id="password"
              name="password"
              className="mt-1 p-2 w-full border rounded-md"
            />
          </div>
          <button
            type="submit"
            className="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600"
          >
            Login
          </button>
        </form>
      </div>
    </div>
  );
};

export default Login;




Step 13: Create Sign Up Page


"use client";
import React from "react";
import { useRouter } from 'next/navigation';
import axios from 'axios';


const Login = () => {
    const router = useRouter();
    const baseURL = process.env.NEXT_PUBLIC_HOSTNAME + "register";

    const submitHandler = async (data) => {

        const requestBody = {
            email: data.email,
            password: data.password
        }

        await axios
            .post(baseURL, requestBody)
            .then(function (res) {
                console.log(res);
                alert('Account created!');
                router.push('/');
            })
            .catch(function (error) {
                alert('Something went wrong...');
            });

    };

    return (
        <div className="min-h-screen flex items-center justify-center bg-gray-50">
            <div className="max-w-md w-full p-6 bg-white rounded-md shadow-md">
                <h2 className="text-2xl font-bold mb-4">Register</h2>
                <form onSubmit={(e) => {
                    e.preventDefault();
                    submitHandler({
                        email: e.target.username.value,
                        password: e.target.password.value,
                    });
                }}>
                    <div className="mb-4">
                        <label
                            htmlFor="username"
                            className="block text-sm font-medium text-gray-600"
                        >
                            Username
                        </label>
                        <input
                            type="text"
                            id="username"
                            name="username"
                            className="mt-1 p-2 w-full border rounded-md"
                        />
                    </div>
                    <div className="mb-4">
                        <label
                            htmlFor="password"
                            className="block text-sm font-medium text-gray-600"
                        >
                            Password
                        </label>
                        <input
                            type="password"
                            id="password"
                            name="password"
                            className="mt-1 p-2 w-full border rounded-md"
                        />
                    </div>
                    <button
                        type="submit"
                        className="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600"
                    >
                        Sign up
                    </button>
                </form>
            </div>
        </div>
    );
};

export default Login;













Watch Next.js 14 Authentication with NextAuth.js and MongoDB, featuring Custom Sign-up and Login Pages Installation



Related Projects


Recent Comments

Latest Comments section by users