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 mongoose
for 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.