Kimkorng

Next.js Authentication: Sign-In and Sign-Up with NextAuth and MongoDB

14 JUN, 2024

Today we'll create a sign-in and sign-up systems using Next.js with NextAuth and MongoDB. To do that, we need to set up our Next.js and config NextAuth for handle authentication, Next we'll connect our application to MongoDB to store and manage user data efficiently. Let's start:

In this guide, We'll use the Next.js App Router for routing and handling pages. And You can find more details in the source code on GitHub

Set Up Next.js Project

First, let's create a new Next.js project and install the required dependencies.

Create a new Next.js project with following commands:

shell
npx create-next-app next-auth-app cd next-auth-app

Install required dependencies:

shell
npm install next-auth mongoose bcryptjs axios
  • next-auth for handling user authentication.
  • mongoose for connecting with MongoDB.
  • bcryptjs for password hashing and safe storing.
  • axios for making HTTP requests from a web browser

Set Up MongoDB

Create a MongoDB Database You can use MongoDB Atlas, a cloud database service, or a local MongoDB instance. Read the MongoDB Atlas getting started guide to set up your database.

Create an Environment File Create a file named .env in the project root directory to store environment variables.

env
MONGODB_URI=mongodb+srv://<username>:<password>@cluster0.mongodb.net/mydatabase?retryWrites=true&w=majority NEXTAUTH_SECRET=your_nextauth_secret

Replace <username> and <password> with your MongoDB Atlas credentials and set a secure value for NEXTAUTH_SECRET.

Configure Mongoose

Create a lib/mongodb.js file to handle the connection to MongoDB.

JavaScript icon
javascript
import mongoose from 'mongoose' export const connectMongoDB = async () => { try { await mongoose.connect(process.env.MONGODB_URI); console.log("Connected to MongoDB"); } catch(error) { console.log("Error connecting to MongoDB: ", error); } }

Create a models/User.js file to defines the User model schema for MongoDB.

JavaScript icon
javascript
import mongoose from 'mongoose'; const UserSchema = new mongoose.Schema({ name: { type: String, required: true }, email: { type: String, required: true, unique: true }, password: { type: String, required: true }, }, { timestamps: true }); export default mongoose.models.User || mongoose.model('User', UserSchema);

Create Sign In and Sign Up Pages UI

Create app/login/page.jsx for the user sign-in page and contains the sign-in form and handles user authentication.

jsx
"use client" import React, { useState } from 'react' import { signIn } from 'next-auth/react'; import { useRouter } from 'next/navigation' export default function SignInPage() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const router = useRouter(); const signInSubmit = async (e)=>{ e.preventDefault(); try { const res = await signIn("credentials", { email, password, redirect: false }) if (res.error) { return; } router.replace("/"); } catch (error) { console.log(error) } } return ( <div> <form onSubmit={signInSubmit} className='flex flex-col'> <h1>Sign In</h1> <input type="text" placeholder='Email' onChange={e=> setEmail(e.target.value)} required /> <input type="password" placeholder='Password' onChange={e=> setPassword(e.target.value)} required /> <button type="submit">Sign in</button> </form> </div> ) }

Create app/register/page.jsx for the user sign-up page and contains the sign-up form and handles new user registration.

jsx
"use client" import React, { useState } from 'react' import axios from 'axios'; export default function SignUpPage() { const [email, setEmail] = useState(''); const [name, setName] = useState(''); const [password, setPassword] = useState(''); const signUpSubmit = async (e)=>{ e.preventDefault(); try { await axios.post('/api/auth/signup', { name, email, password }); router.push('/login'); } catch (error) { console.log(error) } } return ( <div> <form onSubmit={signUpSubmit} className='flex flex-col'> <h1>Sign Up</h1> <input type="text" placeholder='Name' onChange={e=> setName(e.target.value)} required /> <input type="text" placeholder='Email' onChange={e=> setEmail(e.target.value)} required /> <input type="password" placeholder='Password' onChange={e=> setPassword(e.target.value)} required /> <button type="submit">Sign up</button> </form> </div> ) }

Configure Next.js API Routes for User Management

Create app/api/auth/signup/route.js for API route to handles user registration by saving the new user's data to the MongoDB database.

JavaScript icon
javascript
import { NextResponse } from 'next/server'; import bcrypt from 'bcryptjs'; import User from '../../../../../models/user' import { connectMongoDB } from '../../../../../lib/mongodb'; export async function POST(req) { const { name, email, password } = await req.json(); console.log(name, email, password) await connectMongoDB(); const existingUser = await User.findOne({ email }); if (existingUser) { return NextResponse.json({ error: 'User already exists' }, { status: 400 }); } const hashedPassword = bcrypt.hashSync(password, 10); const user = new User({ name, email, password: hashedPassword, }); await user.save(); return NextResponse.json({ message: 'User created successfully' }); }

Configure NextAuth

Create app/api/auth/[...nextauth]/route.js file for configures NextAuth to use the credentials provider and integrates with the MongoDB database for authentication.

JavaScript icon
javascript
import NextAuth from "next-auth/next"; import CredentialsProvider from "next-auth/providers/credentials"; import { connectMongoDB } from "../../../../lib/mongodb"; import User from "../../../../models/User"; import bcrypt from 'bcryptjs' const authOptions = { providers: [ CredentialsProvider({ name: 'credentials', credentials: {}, async authorize(credentials) { const { email, password } = credentials; try { await connectMongoDB(); const user = await User.findOne({ email }); if (!user) { return null; } const passwordMatch = await bcrypt.compare(password, user.password); if (!passwordMatch) { return null; } return user; } catch(error) { console.log("Error: ", error) } } }) ], session: { strategy: "jwt" }, secret: process.env.NEXTAUTH_SECRET, pages: { signIn: "/login" }, callbacks: { async jwt({ token, user, account, profile, isNewUser }) { if (user) { return { ...token, id: user._id, role: user.role } } return token }, async session({ session, user, token }) { return { ...session, user: { ...session.user, id: token.id, role: token.role } } } } } const handler = NextAuth(authOptions); export { handler as GET, handler as POST }

Protect Pages with Authentication

Create app/AuthProvider.jsx component for wraps the application with SessionProvider to manage session state.

jsx
"use client" import { SessionProvider } from "next-auth/react" export const AuthProvider = ({ children }) => { return <SessionProvider>{ children }</SessionProvider> }

Update your app/layout.js by wrapping the application with the AuthProvider component to enable authentication across the app.

JavaScript icon
javascript
// ... export default function RootLayout({ children }) { return ( <html lang="en"> <body className={inter.className}> <AuthProvider> {children} </AuthProvider> </body> </html> ); }

Create app/protected/page.jsx component for protects certain pages and ensures they are only accessible to authenticated users.

JavaScript icon
javascript
"use client" import { useSession } from 'next-auth/react'; export default function Protected({children}) { const { data: session } = useSession(); if (!session) { return <p>You must be signed in to view this page</p>; } return ( <> {children} </> ); }

Run the Project

Start the development server by using the command:

shell
npm run dev

Visit http://localhost:3000 to test the sign-in and sign-up functionality. The protected page should be accessible only after signing in.

SHARE