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:
shellnpx create-next-app next-auth-app cd next-auth-app
Install required dependencies:
shellnpm 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.
envMONGODB_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.
javascriptimport 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.
javascriptimport 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.
javascriptimport { 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.
javascriptimport 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// ... 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"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:
shellnpm 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.