Introduction
What is NextAuth?
NextAuth is a free tool that helps you easily add login features to your Next.js app. It takes care of signing users in, creating accounts, and managing their sessions, so you don’t have to code everything yourself.
It works with:
- Email & passwords
- Social logins (like Google, Facebook, GitHub)
- Magic links (login via email, no password needed)
- Even crypto wallets!
Basically, it handles all the complicated auth stuff for you.
Is NextAuth's Email Provider good to use
Yup, if it fits your app. Here’s the simple version:
It is Good In:
- Passwordless logins – Users just enter their email, get a magic link on Gmail, and click to sign in. No passwords to remember!
- Easy sign-ups – It is great in blogs, newsletters or simple apps to make the users join fast.
- Extra login option – I Provides Extra loign options such as Google, Github, Facebook and many more logins.
👎 Be Careful With:
- Emails going to spam – If you are using Email Provider and users tries to SignIn but you email is blocked, users can't signin
- Slower login process – If your app is all about speed, you should not use it.
- Security risk – If someone hacks a user’s email, they can access their account.
Final Tip:
Use email login for simple, low-risk apps like blogs, forums or newsletter. Avoid it for high-security or fast-access apps like banking or real-time tools.
In This Guide:
Now that you understand the Email Provider and when to use it, we'll walk you through how to set it up in your Next.js app with NextAuth.
Follow these simple steps, and you'll have email login ready in no time!
(Next up: The actual step-by-step setup.)
Why keep it simple?
- No confusing jargon ✅
- Straight to the point ✅
- Easy to follow ✅
Step-by-Step: Add Email Provider in Next.js using NextAuth
Let's start setting it up!
Step 1: Install Multiple Libraries For Email Provider
First, Install the NextAuth package in your Next.js project
npm install next-auth
Second, Install the nodemailer library to send emails directly from your server or backend
npm install nodemailer
Third, Install the MongoDbAdapter library to connect NextAuth to your MongoDb Database to store users, sessions, accounts and verification tokens.
npm install @next-auth/mongodb-adapter
Fourth, Install Mongoose library to define schemas and interacts with Mongodb in a structured key.
npm install mongoose
To Install all these libraries at ones copy and paste this command in your cmd or terminal
npm install next-auth nodemailer @next-auth/mongodb-adapter mongoose
Fifth, Make a folder /lib
in you root folder and file inside
your /lib
folder /db.js
to make the connection with your Mongodb database.
import mongoose from "mongoose";
const Condb = () => {
try {
mongoose.connect(process.env.MONGODB_URI);
console.log("MonogDb is Connected");
} catch (error) {
console.log("MonogDb Error", error);
}
}
export default Condb;
Sixth, Make a file in your /lib
folder named as /mongodb.js
import { MongoClient } from "mongodb";
const uri = process.env.MONGODB_URI;
const options = {};
let client;
let clientPromise;
if (!process.env.MONGODB_URI) {
throw new Error("Please add MONGODB_URI to your .env.local");
}
if (process.env.NODE_ENV === "development") {
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options);
global._mongoClientPromise = client.connect();
}
clientPromise = global._mongoClientPromise;
} else {
client = new MongoClient(uri, options);
clientPromise = client.connect();
}
export default clientPromise;
Step 2: Create The Api Auth Route
Before Step 2 you should have user model containing the schema just like this
This is the demo schema for guidance you can make your own
The schema should contain emailVerified
to make the user verfied. You can add isAdmin
if the user contain's admin role. In this tutorial we will use isAdmin
import mongoose from "mongoose";
const userSchema = new mongoose.Schema({
name: {
type: String,
},
email: {
type: String,
required: true,
unique: true
},
emailVerified: {
type: Date,
default: null
},
isAdmin: {
type: Boolean,
default: false,
required: true
}
}, { timestamps: true })
const User = mongoose.models.User || mongoose.model('User', userSchema);
export default User
Now, Inside your project /pages/api/auth
OR /app/api/auth
, create a folder and file named [...nextauth].js
on pages if you are using app router use [...nextauth]/route.js
.
import Condb from "@/lib/db";
import User from "@/models/user";
import NextAuth from "next-auth";
import EmailProvider from "next-auth/providers/email";
import GithubProvider from 'next-auth/providers/github';
import { MongoDBAdapter } from "@next-auth/mongodb-adapter";
import clientPromise from "@/lib/mongodb";
import { createTransport } from "nodemailer";
export const authOptions = {
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET
}),
EmailProvider({
server: {
host: process.env.EMAIL_SERVER_HOST,
port: process.env.EMAIL_SERVER_PORT,
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD,
},
},
from: process.env.EMAIL_FROM,
async sendVerificationRequest({ identifier, url, provider }) {
const { host } = new URL(url);
const transport = createTransport(provider.server);
await transport.sendMail({
to: identifier,
from: provider.from,
subject: "🔐 Sign in to [Your Company]",
html: `
<div style="background-color: #ebebeb; padding: 40px 20px; font-family: 'Helvetica Neue', sans-serif; color: #004e98;">
<div style="max-width: 500px; background-color: #ffffff; margin: 0 auto; border-radius: 10px; overflow: hidden; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);">
<div style="background-color: #004e98; padding: 20px; text-align: center;">
<h1 style="color: #ffffff; margin: 0; font-size: 24px;">Welcome to <span style="color: #ff6700;">[Your Company]</span> 👋</h1>
</div>
<div style="padding: 30px 20px; text-align: center;">
<p style="font-size: 16px; color: #3a6ea5; margin-bottom: 20px;">You're almost signed in! Click the button below to complete your login:</p>
<a href="${url}" style="display: inline-block; background-color: #ff6700; color: #ffffff; padding: 14px 24px; border-radius: 6px; text-decoration: none; font-weight: bold; font-size: 16px;">Sign in to [Your Company]</a>
<p style="font-size: 14px; color: #c0c0c0; margin-top: 30px;">This link is valid for 10 minutes. If you didn’t request this, feel free to ignore it.</p>
</div>
<div style="background-color: #ebebeb; padding: 15px; text-align: center; font-size: 12px; color: #c0c0c0;">
Powered by [Your Company] • <a href="https://${host}" style="color: #004e98; text-decoration: none;">${host}</a>
</div>
</div>
</div>
`,
text: `Sign in to [Your Company]\n${url}\n\nIf you didn’t request this, you can ignore this email.`,
});
},
}),
],
session: {
strategy: "jwt",
},
adapter: MongoDBAdapter(clientPromise),
callbacks: {
async jwt({ token, user }) {
try {
await Condb();
// Get user from DB using token email (after login or subsequent calls)
const dbUser = await User.findOne({ email: token.email }).lean();
if (dbUser) {
token.isAdmin = dbUser.isAdmin || false;
token.name = dbUser.name;
} else {
token.isAdmin = false;
}
} catch (error) {
console.error('JWT callback error:', error);
token.isAdmin = false;
}
return token;
},
async session({ session, token }) {
session.user.isAdmin = token.isAdmin;
session.user.name = token.name;
return session;
},
async redirect({ url, baseUrl }) {
// Always go to homepage after login
return baseUrl;
},
async signIn({ user, account }) {
try {
await Condb();
const email = user?.email;
if (!email) throw new Error('Invalid user');
const userData = {
email,
name: user.name || user.login || email,
provider: account.provider,
emailVerified: new Date(),
isAdmin: false
};
await User.findOneAndUpdate(
{ email },
{ $setOnInsert: userData },
{ upsert: true, new: true, setDefaultsOnInsert: true }
);
return true;
} catch (error) {
console.error('SignIn error:', error.message);
return '/auth/error?message=Authentication failed';
}
},
},
pages: {
signIn: "/signin",
signOut: "/",
error: "/auth/error",
},
secret: process.env.NEXTAUTH_SECRET,
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
Now, Inside your components folder make a file /SessionWrapper.js
"use client";
import React from 'react'
import { SessionProvider } from "next-auth/react"
const SessionWrapper = ({ children }) => {
return (
<SessionProvider>
{children}
</SessionProvider>
)
}
export default SessionWrapper
Wrap your children with SessionWrapper.js
component in your layout.js file
<html lang="en">
<body>
<SessionWrapper>
{children}
</SessionWrapper>
</body>
</html>
Great, You have completed Step 2.
Step 3: Set up Environment Variables
In your .env
file or .env.local
file, add:
# Github credentials
GITHUB_ID= Add Your Github auth ID
GITHUB_SECRET= Add Your Github auth Secret
# Next Auth Credentials
NEXTAUTH_URL=http://localhost:3000/ #Your website URL
NEXTAUTH_SECRET= Your NextAuth Secret
# Generate NextAuth secret using this command in bash openssl rand -base64 32 Or if you don't have bash use powershell -Command "[convert]::ToBase64String((1..32 | ForEach-Object {Get-Random -Maximum 256}))" in your powershell
# Database Credentials
MONGODB_URI=mongodb://localhost:27017/Laivor #Or you can add mongodb atlas URI
# Email Credentials
EMAIL_SERVER_HOST=smtp.your-email-provider.com
EMAIL_SERVER_PORT=587
EMAIL_SERVER_USER=your@email.com
EMAIL_SERVER_PASSWORD=your-password
EMAIL_FROM=your@email.com
Now Create a /verify-admin
page in your /auth
To check if the current user is an admin based on their session.
import { getServerSession } from "next-auth/next";
import { NextResponse } from "next/server";
import { authOptions } from "../[...nextauth]/route";
export async function GET() {
try {
const session = await getServerSession(authOptions);
if (!session?.user?.isAdmin) {
return NextResponse.json({ isAdmin: false }, { status: 403 });
}
return NextResponse.json({ isAdmin: true });
} catch (error) {
return NextResponse.json({ error: "Internal Server Error" }, { status: 500 });
}
}
Step 4: Setting up FrontEnd
Now, we’ve completed the backend setup, our next task is to make it work from the frontend.
In your /signin
page
'use client';
import React, { useState } from 'react';
import { signIn } from 'next-auth/react';
import { useRouter } from 'next/navigation';
const SigninPage = () => {
const router = useRouter();
const [formData, setFormData] = useState({
email: '',
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleProviderLogin = (provider) => {
console.log(`Logging in with ${provider} using email: ${formData.email}`);
};
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium mb-2 text-[#555] dark:text-[#c0c0c0]">
Email Address
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<FaEnvelope className="text-[#555] dark:text-[#c0c0c0]" />
</div>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
className="w-full pl-10 pr-4 py-3 rounded-lg border border-[#c0c0c0] dark:border-[#333] bg-white dark:bg-[#0d0d0d] focus:outline-none focus:ring-2 focus:ring-[#ff6700]"
placeholder="john@example.com"
required
/>
</div>
</div>
<div className="space-y-4">
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={async () => {
if (!formData.email) return alert("Please enter your email");
const res = await signIn("email", {
email: formData.email,
redirect: false,
callbackUrl: "/",
});
if (res?.ok) {
router.push(`/verify-request?email=${encodeURIComponent(formData.email)}`);
} else {
alert("Failed to send email. Please try again.");
}
}}
className="w-full flex items-center justify-center gap-3 bg-[#ff6700] hover:bg-[#e85d00] text-white py-3 rounded-lg font-medium transition-colors shadow-lg cursor-pointer"
>
<FaEnvelope className="text-xl" /> Sign in with Email Link
</motion.button>
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => signIn('github', { callbackUrl: '/' })}
className="w-full flex items-center justify-center gap-3 bg-[#333] hover:bg-[#242424] text-white py-3 rounded-lg font-medium transition-colors shadow-lg cursor-pointer"
>
<FaGithub className="text-xl" /> Sign in with GitHub
</motion.button>
</div>
</div>
);
};
export default SigninPage;
In your /signup
page
'use client';
import React, { useState } from 'react';
import { signIn } from 'next-auth/react';
import { useRouter } from 'next/navigation';
import { signIn } from 'next-auth/react';
const SignupPage = () => {
const router = useRouter();
const [formData, setFormData] = useState({
email: '',
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleProviderSignup = (provider) => {
console.log(`Signing up with ${provider} using email: ${formData.email}`);
};
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium mb-2 text-[#555] dark:text-[#c0c0c0]">
Email Address
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<FaEnvelope className="text-[#555] dark:text-[#c0c0c0]" />
</div>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
className="w-full pl-10 pr-4 py-3 rounded-lg border border-[#c0c0c0] dark:border-[#333] bg-white dark:bg-[#0d0d0d] focus:outline-none focus:ring-2 focus:ring-[#ff6700]"
placeholder="john@example.com"
required
/>
</div>
</div>
{/* Updated Security Notice */}
<div className="bg-[#fff8e6] dark:bg-[#2d2400] p-4 rounded-lg border border-[#ffd54f] dark:border-[#ffc107]">
<div className="flex items-start">
<FaExclamationTriangle className="text-[#ff6700] text-xl mt-0.5 mr-3 flex-shrink-0" />
<div>
<h3 className="font-bold text-[#004e98] dark:text-[#3a6ea5] mb-1">Secure Sign Up</h3>
<p className="text-sm text-[#555] dark:text-[#c0c0c0]">
We use OAuth for secure authentication. Your password is never shared with us,
and you benefit from the security measures of your email provider.
</p>
</div>
</div>
</div>
<div className="flex items-center">
<input
type="checkbox"
id="terms"
className="w-4 h-4 text-[#ff6700] bg-gray-100 border-gray-300 rounded focus:ring-[#ff6700]"
required
/>
<label htmlFor="terms" className="ml-2 text-sm text-[#555] dark:text-[#c0c0c0]">
I agree to the <Link href="#" className="text-[#004e98] dark:text-[#3a6ea5] hover:underline">Terms of Service</Link> and <Link href="#" className="text-[#004e98] dark:text-[#3a6ea5] hover:underline">Privacy Policy</Link>
</label>
</div>
<div className="space-y-4">
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={async () => {
if (!formData.email) return alert("Please enter your email");
const res = await signIn("email", {
email: formData.email,
redirect: false,
callbackUrl: "/",
});
if (res?.ok) {
router.push(`/verify-request?email=${encodeURIComponent(formData.email)}`);
} else {
alert("Failed to send email. Please try again.");
}
}}
className="w-full flex items-center justify-center gap-3 bg-[#ff6700] hover:bg-[#e85d00] text-white py-3 rounded-lg font-medium transition-colors shadow-lg cursor-pointer"
>
<FaEnvelope className="text-xl" /> Sign up with Email Link
</motion.button>
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => signIn('GitHub')}
className="w-full flex items-center justify-center gap-3 bg-[#333] hover:bg-[#242424] text-white py-3 rounded-lg font-medium transition-colors shadow-lg cursor-pointer"
>
<FaGithub className="text-xl" /> Sign up with GitHub
</motion.button>
</div>
</div>
There we goooo! 🎉 We’ve completed the setup. Now, if you want to protect your SignIn, SignUp and admin route — let’s lock it down! 🔐
First, you have a create a middleware file in you root middleware.js
import { getToken } from "next-auth/jwt";
import { NextResponse } from "next/server";
export async function middleware(req) {
const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
const url = req.nextUrl;
// Redirect logged-in users away from /signin
if ((url.pathname === "/signin" || url.pathname === "/signup") && token) {
return NextResponse.redirect(new URL("/", req.url));
}
// Protect /admin and /comment
if ((url.pathname === "/admin" || url.pathname === "/comment") && (!token || !token.isAdmin)) {
return NextResponse.redirect(new URL("/signin", req.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/signin", "/signup", "/admin", "/comment"],
};
🎉 That’s a Wrap!
You’ve now successfully added Email Provider authentication and oAuth in your Next.js app using NextAuth — and even protected your admin routes!
Authentication doesn’t have to be scary or complicated. With the right tools (and the right guide ;)), you’re in full control.
Have questions? Feel free to Contact Us.
Want more? Stay tuned for upcoming articles on How to apply Dark Theme in Nextjs! 🚀
Happy coding! 💻✨