Next-Auth is a versatile authentication library designed for Next.js applications. With the release of Next-Auth 5, it offers even more streamlined integrations and enhanced security features. Next-Auth Provide multiple authentication providers like login with google, facebook, github, apple, linkedin, credentials (username/email & password) etc. Here we will setup authentication using the Credential Provider in Next-Auth 5, focusing on username and password authentication.
Table of Contents
Next Auth Authentication
Prerequisites
Before starting, ensure you have the following:
- Node.js installed on your machine.
- Must know the nextJS and reactjs , check the reactjs basic
- A Next.js project initialized. If you don’t have one, create it by running:
npx create-next-app@latest my-next-auth cd my-next-auth
After running above command you will get a simple nextjs application with very basic home page. you can test that home page by runnin
npm run dev
It will run the application at http:localhost:3000 you will see the preview there.
If you already have next-auth implemented and looking for upgrad checkout the offical upgrading guide to next-auth v5
Next-auth authentication steps :
Step 1: Install Next-Auth
Begin by installing Next-Auth and the necessary dependencies, Here we will use mongoose as the database dependency.
npm install next-auth
npm install mongoosefor more details checkout next-auth v5 installation guide
Step 2 : Create User model
First create a database connection file using mongoose inside utils/db.ts :
import mongoose, { Connection } from 'mongoose';
interface MongooseCache {
conn: Connection | null;
promise: Promise<Connection> | null;
}
// Ensure the global object has a `mongoose` property for the cache
declare global {
// eslint-disable-next-line no-var
var mongoose: MongooseCache;
}
let cached: MongooseCache;
if (!global.mongoose) {
global.mongoose = { conn: null, promise: null };
}
cached = global.mongoose;
async function connect(): Promise<Connection> {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
const opts = {
useNewUrlParser: true,
useUnifiedTopology: true,
bufferCommands: false,
};
cached.promise = mongoose.connect(process.env.MONGO_URL as string, opts).then((mongoose) => mongoose.connection).catch((err) => {
throw err;
});
}
cached.conn = await cached.promise;
return cached.conn;
}
export default connect;Now create user.ts file Inside /models to make a user model so we can authenticate using next-auth v5
import mongoose, { Document, Model, Schema } from "mongoose";
// Define an interface for the User document
interface IUser extends Document {
email: string;
password: string;
}
// Define the User schema
const userSchema: Schema<IUser> = new Schema(
{
email: {
type: String,
unique: true,
required: true,
},
password: {
type: String,
required: false,
},
},
{ timestamps: true }
);
// Define the User model
const User: Model<IUser> = mongoose.models.User || mongoose.model<IUser>("User", userSchema);
export default User;Step 3: Configure for Next-Auth authentication
Next-Auth authentication requires a configuration file to set up your authentication providers. Create a new file named nextAuthConfig.ts
The authorize function is responsible for authenticating the user. Implement the authenticateUser function to verify the username and password against your user database
import {AuthOptions} from "next-auth";
import { Account, User as AuthUser } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import bcrypt from "bcryptjs";
import User from "@/models/User";
import connect from "@/utils/db";
export const authOptions: AuthOptions = {
// Configure one or more authentication providers
providers: [
CredentialsProvider({
id: "credentials",
name: "Credentials",
credentials: {
email: { label: "Email", type: "text" },
password: { label: "Password", type: "password" },
},
async authorize(credentials: any) {
await connect();
try {
const user = await User.findOne({ email: credentials.email });
if (user) {
const isPasswordCorrect = await bcrypt.compare(
credentials.password,
user.password
);
if (isPasswordCorrect) {
return user;
}
}
} catch (err: any) {
throw new Error(err);
}
},
}),
],
secret: process.env.NEXTAUTH_SECRET,
session: {
strategy: "jwt",
},
callbacks: {
async jwt(token, user) {
if (user) {
token.id = user.id;
}
return token;
},
async session(session, token) {
session.user.id = token.id;
return session;
}
},
};[...nextauth].ts in the app/api/auth directory or use below commands:
mkdir -p app/api/auth/[...nextauth] touch app/api/auth/[...nextauth]/route.ts
In this file, configure the Credential Provider for username and password authentication.
import NextAuth from "next-auth";
import { authOptions } from "@/utils/nextAuthConfig"
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
Step 4: Create a Sign-In Page
Next-Auth allows you to create a custom sign-in page. We will use the app router so Create a new file auth/signin/page.tsx:
import { signIn } from "next-auth/react";
import { useState, FormEvent, ChangeEvent } from "react";
export default function SignIn() {
const [username, setUsername] = useState<string>("");
const [password, setPassword] = useState<string>("");
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
const res = await signIn("credentials", {
redirect: false,
username,
password,
});
if (res?.ok) {
window.location.href = "/";
} else {
console.error("Error signing in");
}
};
const handleUsernameChange = (e: ChangeEvent<HTMLInputElement>) => {
setUsername(e.target.value);
};
const handlePasswordChange = (e: ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value);
};
return (
<form onSubmit={handleSubmit}>
<label>
Username
<input
type="text"
name="username"
value={username}
onChange={handleUsernameChange}
/>
</label>
<label>
Password
<input
type="password"
name="password"
value={password}
onChange={handlePasswordChange}
/>
</label>
<button type="submit">Sign In</button>
</form>
);
}
check out the Dynamic Breadcrumb using React Router v6
Step 5: Protect Pages
To protect pages and require authentication, use the useSession hook provided by Next-Auth. Create a new file admin/protected.js:
import { useSession, getSession } from "next-auth/react";
import { GetServerSideProps } from "next";
import { Session } from "next-auth";
export default function ProtectedPage() {
const { data: session } = useSession();
if (!session) {
return <p>Access Denied</p>;
}
return (
<div>
<h1>Protected Page</h1>
<p>Welcome, {session.user?.name}</p>
</div>
);
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const session: Session | null = await getSession(context);
if (!session) {
return {
redirect: {
destination: '/auth/signin',
permanent: false,
},
};
}
return {
props: { session },
};
};
Step 6: Protect EndPoints – Next-Auth
Protect using APIs
import { NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { authOptions } from "@/utils/nextAuthConfig";
export const GET = async (request: Request) => {
const session = await getServerSession(authOptions)
if(session){
// write the API logic
}
else{
return new NextResponse(
JSON.stringify({ message: "unAuthorized" }),
{ status: 401 }
);
}
}
export const POST = async (request: Request) => {
const session = await getServerSession(authOptions)
if(session){
// write the API logic
}
else{
return new NextResponse(
JSON.stringify({ message: "unAuthorized" }),
{ status: 401 }
);
}
}
Protect Using Next-Auth middleware :
Apply authentication protection using next-auth middleware:
import type { NextRequest } from "next/server";
import createIntlMiddleware from "next-intl/middleware";
import { auth } from "@/auth";
import { defaultLocale, localePrefix, locales } from "./messages/config";
const publicPages = [
"/",
"/sign-in",
];
const intlMiddleware = createIntlMiddleware({
locales,
defaultLocale,
localePrefix
});
const authMiddleware = auth((req: NextRequest) => {
const session = (req as any).auth; // Adjust this if you have a proper type for req.auth
if (session) {
return intlMiddleware(req);
}
});
export default function middleware(req: NextRequest) {
const publicPathnameRegex = new RegExp(
`^(/(${locales.join("|")}))?(${publicPages.flatMap((p) => (p === "/" ? ["", "/"] : p)).join("|")})/?$`,
"i"
);
const isPublicPage = publicPathnameRegex.test(req.nextUrl.pathname);
if (isPublicPage) {
return intlMiddleware(req);
} else {
return (authMiddleware as any)(req);
}
}
export const config = {
matcher: ["/((?!api|_next|.*\\..*).*)"]
};
Conclusion
Implementing authentication in your Next.js application with Next-Auth 5 and the Credential Provider is straightforward. By following these steps, you can set up username and password authentication, create a custom sign-in page, and protect your pages to ensure only authenticated users can access them. This setup provides a solid foundation for integrating more complex authentication flows and enhancing the security of your application.

