Easy Way – Next-Auth Authentication v5 with Credential Provider

Next-Auth authentication

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.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.