When we build a full stack app, we need a way for users to login and stay logged in.
This is called authentication.
In this blog we will understand how to use NextAuth in Next.js in a very simple way π
π What is NextAuth
NextAuth is a library that helps us:
β Login users
β Manage sessions
β Protect pages
π Example
User enters email and password
App checks database
If correct β user is logged in β
π¦ Step 1 Install NextAuth
Run this command:
npm install next-auth@4
π Step 2 Add Secret Key
Create or open .env file
NEXTAUTH_SECRET=mySuperSecretKey123
π This is used to secure login sessions
π§ Step 3 Add TypeScript Support
Create file:
next-auth.d.ts
Add:
declare module "next-auth" {
interface Session {
user: {
id: string;
} & DefaultSession["user"];
}
}
π Now we can use:
session.user.id
βοΈ Step 4 Create Auth Configuration
Create file:
lib/auth.ts
import CredentialsProvider from "next-auth/providers/credentials";
import { connectToDatabase } from "./db";
import User from "@/models/User";
import bcrypt from "bcryptjs";
export const authOptions = {
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: { label: "Email", type: "text" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
throw new Error("Missing email or passsword");
}
try {
await connectToDatabase();
const user = await User.findOne({ email: credentials.email });
if (!user) {
throw new Error("No user found with this");
}
const isValid = await bcrypt.compare(
credentials.password,
user.password
);
if (!isValid) {
throw new Error("invalid password");
}
return {
id: user._id.toString(),
email: user.email,
};
} catch (error) {
console.error("Auth error: ", error);
throw error;
}
},
}),
],
callbacks: {
async jwt({ token, user }: { token: any; user?: any }) {
if (user) {
token.id = user.id;
}
return token;
},
async session({ session, token }: { session: any; token: any }) {
if (session.user) {
session.user.id = token.id as string;
}
return session;
},
},
pages: {
signIn: "/login",
error: "/login",
},
session: {
strategy: "jwt" as const,
maxAge: 30 * 24 * 60 * 60,
},
secret: process.env.NEXTAUTH_SECRET,
};
This file contains all login logic.
π Step 5 Credentials Provider
CredentialsProvider({
name: "Credentials",
π This tells NextAuth:
π¬ βI will login users using email and passwordβ
π Step 6 Authorize Function (Main Logic)
async authorize(credentials)
π This runs when user tries to login
π§ͺ Example Input
{
"email": "test@gmail.com",
"password": "123456"
}
π§ What happens inside authorize
1οΈβ£ Check input
if (!credentials?.email || !credentials?.password)
π If missing β error
2οΈβ£ Connect database
await connectToDatabase();
π Connects to MongoDB
3οΈβ£ Find user
const user = await User.findOne({ email: credentials.email });
π Check if user exists
4οΈβ£ If user not found
throw new Error("No user found")
5οΈβ£ Compare password
bcrypt.compare(credentials.password, user.password)
π Checks password safely π
6οΈβ£ If correct
return {
id: user._id.toString(),
email: user.email,
};
π Login success π
π Step 7 JWT Callback
async jwt({ token, user })
π Runs after login
token.id = user.id;
π Saves user id in token
π€ Step 8 Session Callback
async session({ session, token })
session.user.id = token.id;
π Now you can access:
session.user.id
π Step 9 Custom Pages
pages: {
signIn: "/login",
error: "/login",
}
π Redirects user to login page
β³ Step 10 Session Setup
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60,
}
π User stays logged in for 30 days
π Step 11 Create API Route
Path:
app/api/auth/[...nextauth]/route.ts
Code:
import { authOptions } from "@/lib/auth";
import NextAuth from "next-auth/next";
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
π This handles all auth requests
π‘ Step 12 Middleware (Protect Routes)
Create file:
middleware.ts
import withAuth from "next-auth/middleware";
import { NextResponse } from "next/server";
export default withAuth(
function middleware(){
return NextResponse.next()
},
{
callbacks: {
authorized: ({token, req}) => {
const {pathname} = req.nextUrl;
// allow auth related routes
if(
pathname.startsWith("/api/auth") ||
pathname === "/login" ||
pathname === "/register"
) {
return true
}
//public routes
if(pathname === "/" || pathname.startsWith("/api/videos")){
return true
}
return !!token
}
}
}
)
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico|public/).*)"],
};
π§ What middleware does
π Runs on every request
π Checks if user is logged in
β Allow public routes
if (
pathname.startsWith("/api/auth") ||
pathname === "/login" ||
pathname === "/register"
)
π These routes are open
π Public pages
if (pathname === "/" || pathname.startsWith("/api/videos"))
π Anyone can access
π Protect other routes
return !!token
π Only logged in users allowed
π§ͺ Example Flow
π User opens:
/dashboard
β If logged in β access
β If not β redirect to login
βοΈ Matcher Config
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico|public/).*)"],
};
π Middleware runs on all important routes
π― Final Flow (Easy Understanding)
π User enters email and password
β‘οΈ authorize() runs
β‘οΈ database check
β‘οΈ password match
β‘οΈ token created
β‘οΈ session created
β‘οΈ user logged in
π¦ Example Stored User
{
"email": "test@gmail.com",
"password": "$2a$10$hashedpassword"
}
π Final Summary
In this setup we learned:
β How login works
β How NextAuth handles authentication
β How to connect database
β How to protect routes
β How to manage session
π Final Thought
NextAuth makes authentication simple.
Instead of building everything from scratch, you can:
π focus on logic
π keep your app secure
π build faster
Happy coding π»β¨
Top comments (0)