Next.js Authentication with Auth0 and MongoDB

Enaikeleomoh
Tech Notions
Published in
8 min readApr 11, 2024

Auth0 is an authentication and authorization library that helps you manage users in Next.JS through seamless signup and login experiences. For an alternative, you can use Firebase or Clerk for authentication

Pre-requisites:

  • Node ≥ 12
  • Basic Next.js knowledge
  • React and React Hooks basic knowledge

Introduction

In this tutorial, we will create a login system using Next.js and Auth0. Auth0 offers a comprehensive authentication solution with pre-built UI components. It has seamless integrations with email and social platforms like Google, Facebook, and GitHub.

We’ll develop a secure authentication system while ensuring that user data is securely stored in a MongoDB database.

Setting up Next.Js. and Auth0

Step 1: Create a new Next.js application

To create a new Next.js project, make sure you have Node.js installed, and then run the following commands:

npx create-next-app mynextjs
cd mynextjs

After successfully creating the Next.js application, the folder structure is as below:

Next.Js project structure

Step 2:Install the Auth0 Next.js SDK

Run the following command within your project directory to install the Auth0 Next.js SDK:

npm install @auth0/nextjs-auth0

Step 3: Add the file.env.local with the following environment variables in the root directory of your project:

AUTH0_SECRET=******************************
AUTH0_BASE_URL=http://localhost:3000
AUTH0_ISSUER_BASE_URL='https://yourdomainfromauth0.us.auth0.com'
AUTH0_CLIENT_ID=add_your_client_id
AUTH0_CLIENT_SECRET=add_your_client_secret

NOTE:

AUTH0_SECRET: A secret key used for authentication with Auth0, generated using openssl rand -hex 32.

AUTH0_BASE_URL: The base URL of your application, typically http://localhost:3000 when running locally.

AUTH0_ISSUER_BASE_URL: The issuer URL of the Auth0 Authorization Server, found in the domain settings.

AUTH0_CLIENT_ID: The Client ID of your Auth0 application.

AUTH0_CLIENT_SECRET: The Client Secret of your Auth0 application.

Step 4: Create a dynamic router within your app directory app/api/auth/[auth0]/route.js

Within the particularroute.js file, add the following code:

import { handleAuth } from '@auth0/nextjs-auth0';

export const GET = handleAuth();

Step 5: Add the UserProvider component to your rootapp/layout.jsx

import { UserProvider } from '@auth0/nextjs-auth0/client';

export default function RootLayout({ children }) {
return (
<html lang="en">
<UserProvider>
<body>{children}</body>
</UserProvider>
</html>
);
}

Step 6: Add Login / logout to Your app/page.js file


import { useState } from "react";

export default function Home() {
const { user, error, isLoading } = useUser();

return (
// IF USER IS LOGGED IN, DISPLAY THIS
user ? (
<div>
<h1>Logged in</h1>
<div className=" mt-[8rem] ">
<h1>Do you wish to log out? </h1>
<button className=" bg-white p-2 px-4 rounded-lg">
{/* ROUTE TO LOGOUT */}
<a href="/api/auth/logout">Logout</a>;
</button>
</div>
</div>
) :
// IF USER IS LOGGED OUT, DISPLAY THIS
<div className=" bg-blue-900 w-full h-[100vh] justify-center items-center flex">
< div className=" text-center w-full" >
<button className=" bg-white p-2 px-4 rounded-lg">
{/* ROUTE TO LOGIN */}
<a href="/api/auth/login">Login</a>;
</button>
</div >
</div >
);
}

Step 7: Implement the logic to store the user information in the MongoDB database after signing in

 useEffect(() => {

const sendUserDataToServer = async () => {
// if (user && user.email && user.name && !requestSent) {
// console.log('Sending user data:', user);

try {

const response = await fetch('/api/storeUser', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username: user.name, email: user.email }),
});

if (response.ok) {
const token = await response.json();
console.log(token);
setUserToken(token)
addUserDetails(user)
addUserToken(token)
console.log(userInfo);
console.log('User data stored successfully.');
}
} catch (error) {
console.error('Error storing user data:', error);
} finally {

setRequestSent(true);
}
}


sendUserDataToServer();
}, [user?.name])

Note: The overall client-side code looks like this:

import { useState,useEffect } from "react";

export default function Home() {
const { user, error, isLoading } = useUser();


useEffect(() => {

const sendUserDataToServer = async () => {

try {

const response = await fetch('/api/storeUser', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username: user.name, email: user.email }),
});

if (response.ok) {
console.log(response.data);
console.log('User data stored successfully.');
}
} catch (error) {
console.error('Error storing user data:', error);
}
}

sendUserDataToServer();
}, [user?.name])

return (
// IF USER IS LOGGED IN, DISPLAY THIS
user ? (
<div>
<h1>Logged in</h1>
<div className=" mt-[8rem] ">
<h1>Do you wish to log out? </h1>
<button className=" bg-white p-2 px-4 rounded-lg">
{/* ROUTE TO LOGOUT */}
<a href="/api/auth/logout">Logout</a>;
</button>
</div>
</div>
) :
// IF USER IS LOGGED OUT, DISPLAY THIS
<div className=" bg-blue-900 w-full h-[100vh] justify-center items-center flex">
< div className=" text-center w-full" >
<button className=" bg-white p-2 px-4 rounded-lg">
{/* ROUTE TO LOGIN */}
<a href="/api/auth/login">Login</a>;
</button>
</div >
</div >
);
}

Step 8: Set up the MongoDB cluster

Log in to MongoDB and set up a MongoDB cluster on Atlas Go to Projects and create a new project.

We’ll add rights to our project and be designated as the project owner. Next, set up a database for our project.

Click on Build a database. Therefore, choosing our database plan will be required of us, so you can use the free shared plan.

Next, we select the providers and regions for our database cluster; you can leave it as is and use the M0 cluster, which is free.

Press the “Create Cluster” button. Our database security will need to be configured next. In the “Where would you like to connect from?” window, select “My Local Environment” and enter your current IP address after selecting a username and password. Select “Finish” by clicking.

Obtaining our connection URL is the next step in connecting to our cluster. Select Connect your application.

Copy the connection string

Step 9: Add the connection string to your .env.local file

Note: Ensure that your connection string has your unique username and password.

// .env

DATABASE_URL=mongodb+srv://username:password@cluster0.ba9ic.mongodb.net/?retryWrites=true&w=majority

AUTH0_SECRET=******************************
AUTH0_BASE_URL=http://localhost:3000
AUTH0_ISSUER_BASE_URL='https://yourdomainfromauth0.us.auth0.com'
AUTH0_CLIENT_ID=add_your_client_id
AUTH0_CLIENT_SECRET=add_your_client_secret

Step 10: Implement the connection to MongoDB within the app/api/storeUser/route.js

Note: Ensure that you add your unique database name.

import { MongoClient } from "mongodb";
import { NextResponse } from "next/server";

async function connectToMongoDB() {
const client = await MongoClient.connect(process.env.DATABASE_URL);
return client.db("myFirstDatabase");
}

Step 11: Check if the user exists first before storing another user in the database.

Note: write the function within the app/api/storeUser/route.js file

async function checkUserExistence(email, db) {
return await db.collection("users").findOne({ email });
}

Step 12:Store the user information in the database if the user passes the check.

Note: write the function within the app/api/storeUser/route.js file

async function createUser(email, username, db) {
const userData = await db.collection("users").insertOne({
email,
username,
});
return userData;
}

Step 13:Store the user information in the database if the user passes the check.

Note: write the function within the app/api/storeUser/route.js file

export const POST = async (req, res) => {
try {
// Extract username and email from the request body
const { username, email } = await req.json();

// Check if username and email are provided
if (!username || !email) {
// Return a response indicating that username and email are required
return new NextResponse("Username and email are required.", { status: 400 });
}

// Connect to MongoDB
const db = await connectToMongoDB();

Check if the user already exists in the database
const existingUser = await checkUserExistence(email, db);
if (existingUser) {
// If the user already exists, return a response indicating so
console.log("User already exists.");
return new NextResponse("User already exists.", { status: 400 });
}

If the user doesn't exist, create a new user in the database
const userData = await createUser(email, username, db);
console.log(userData);

// Return a success response indicating that the user was stored successfully
return new NextResponse({ message: 'User stored successfully.', userData }, { status: 201 });
} catch (e) {
If an error occurs during the process, log the error and return an internal server error response
console.error(e);
return new NextResponse("Internal Server Error", { status: 500 });
}
};

the overall server-side code looks like this within the app/api/storeUser/route.js file

import { MongoClient } from "mongodb";
import { NextResponse } from "next/server";

async function connectToMongoDB() {
const client = await MongoClient.connect(process.env.MONGODB_URI);
return client.db("ecommerce");
}

async function checkUserExistence(email, db) {
return await db.collection("users").findOne({ email });
}

async function createUser(email, username, db) {
const userData = await db.collection("users").insertOne({
email,
username,
isAdmin: false,
});
return userData;
}

export const POST = async (req, res) => {
try {
// Extract username and email from the request body
const { username, email } = await req.json();

// Check if username and email are provided
if (!username || !email) {
// Return a response indicating that username and email are required
return new NextResponse("Username and email are required.", { status: 400 });
}

// Connect to MongoDB
const db = await connectToMongoDB();

Check if the user already exists in the database
const existingUser = await checkUserExistence(email, db);
if (existingUser) {
// If the user already exists, return a response indicating so
console.log("User already exists.");
return new NextResponse("User already exists.", { status: 400 });
}

If the user doesn't exist, create a new user in the database
const userData = await createUser(email, username, db);
console.log(userData);

// Return a success response indicating that the user was stored successfully
return new NextResponse({ message: 'User stored successfully.', userData }, { status: 201 });
} catch (e) {
If an error occurs during the process, log the error and return an internal server error response
console.error(e);
return new NextResponse("Internal Server Error", { status: 500 });
}
};

Conclusion

Combining Next.js with Auth0 helps implement fast and secure authentication in web apps. In this tutorial, we successfully developed a login system using Auth0’s authentication capabilities.

We also ensured that important user data was securely stored in a MongoDB database.

This integration offers a robust foundation for building secure and user-friendly applications with Next.js.

--

--