Implementing User Authentication with Google, GitHub, and Custom Credentials in a Next.js App with NextAuth.js

Discover how NextAuth.js brings seamless authentication to your web projects with Google, GitHub, and custom credentials.

·

17 min read

Implementing User Authentication with Google, GitHub, and Custom Credentials in a Next.js App with NextAuth.js

Introduction

Welcome aboard! Today, we're diving into the fascinating world of authentication, where user security meets smooth experiences. Imagine a key that effortlessly unlocks different doors – that's what NextAuth.js does for our digital spaces. In this adventure, we'll explore how NextAuth.js works its magic with big names like Google and GitHub, as well as custom credentials. And the best part? We're keeping things snappy and stylish with Prisma, MongoDB, and Next.js. Whether you're a tech guru or just curious, get ready to unravel the secrets behind user logins and create a safer, friendlier digital world. Ready? Let's roll!

Project Overview

Get ready to journey through a project where the spotlight shines on authentication prowess. Here's the twist: we've kept the UI sleek and minimal. Our focus? Unleash the capabilities of NextAuth.js, seamlessly weaving together Google, GitHub, and custom credentials into a cohesive authentication experience. But wait, there's more – with Prisma, MongoDB, and Next.js in our arsenal, we've designed an interface that lets authentication take the stage. Yes, you read that right – while UI takes a back seat, our tech-stack synergy ensures security and seamlessness like never before. Join us as we explore the realm where user interaction meets cutting-edge authentication, without the frills. Ready to dive in? Let's go!

To see the project in action, check out this video walkthrough: Video Link

Exploring NextAuth.js: Your Authentication Ally

In this section, we'll dive headfirst into the heart of NextAuth.js, your ultimate ally in the world of secure user authentication. Imagine a tool that takes the complexities out of integrating various authentication providers, allowing you to focus on what truly matters – creating exceptional user experiences.

NextAuth.js acts as your guide through the labyrinth of OAuth 2.0 protocols, making the integration of Google, GitHub, and custom credentials a breeze. Say goodbye to the headache of managing tokens and callbacks; NextAuth.js handles it all behind the scenes, empowering you to deliver seamless and reliable authentication.

But that's not all. With NextAuth.js by your side, you're not just building a login system – you're building trust. Users can confidently engage with your application, knowing their data is protected and their experiences are optimized. So, let's embark on this journey together and uncover how NextAuth.js transforms the way we approach authentication. Get ready to witness the magic unfold!

Exploring NextAuth.js with MongoDB and Prisma: A Step-by-Step Guide

Are you ready to embark on a journey into the world of seamless authentication? In this blog post, I'll walk you through my experience of integrating NextAuth.js, MongoDB, and Prisma to create a powerful and secure authentication system. So, let's dive right in and follow the steps that led to this authentication triumph!

creating a new Next.js project using the latest version of Next.js. Please make sure you have Node.js and npm (or yarn) installed on your system before you begin. Here are the steps:

Step 1: Create a New Next.js Project Open your terminal and run the following commands:

npx create-next-app@latest

Step 2: Navigate to Your Project Directory Navigate to your project directory using the following command:

cd my-nextjs-project

Step 3: Start the Development Server Start the development server by running the following command:

npm run dev

This will start the Next.js development server. You can access your project by opening a web browser and navigating to localhost:3000.

Step 1: MongoDB Configuration

The adventure begins with setting up MongoDB, a versatile NoSQL database that becomes the bedrock of our authentication system. I configured my .env file with the database URL:

DATABASE_URL="mongodb+srv://<yourdatabase>:<yourpassword>@nextauth.klvzp8g.mongodb.net/<yourdatabase>"

This URL establishes a connection to my MongoDB cluster, setting the stage for data storage and retrieval.

Step 2: Prisma Schema and Configuration

To install the latest versions of Prisma, NextAuth.js, and @prisma/client, you can use the following commands:

# Install Prisma
npm install prisma@latest

# Install NextAuth.js
npm install next-auth@latest

# Install Prisma Client
npm install @prisma/client@latest

This will install the latest versions of these packages in your project. Make sure to run these commands in your project directory. Once the installations are complete, you can proceed with setting up and configuring NextAuth.js and Prisma as needed for your authentication and database needs.

  1. Initialize Prisma: First, you need to initialize Prisma by running the following command in your project directory:
npx prisma init

This command will guide you through the setup process, create the necessary configuration files, and set up the prisma directory.

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mongodb"
  url      = env("DATABASE_URL")
}

model User {
  id             String    @id @default(auto()) @map("_id") @db.ObjectId
  name           String?
  email          String?   @unique
  emailVerified  DateTime?
  image          String?
  address        String?
  hashedPassword String?
  createdAt      DateTime  @default(now())
  updatedAt      DateTime  @updatedAt

  accounts Account[]
}

model Account {
  id                String  @id @default(auto()) @map("_id") @db.ObjectId
  userId            String  @db.ObjectId
  type              String
  provider          String
  providerAccountId String
  refresh_token     String? @db.String
  access_token      String? @db.String
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String? @db.String
  session_state     String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}
  1. Define Database Schema: You can define your database schema in the schema.prisma file inside the prisma directory. You can create models, specify fields, relationships, and other database settings in this file. You mentioned that your full schema models are available in your GitHub repository under prisma.schema, so you can reference that for the schema structure.

  2. Reflect Changes: After making changes to your schema.prisma file, you need to apply these changes to your database. Use the following commands to update your database schema:

npx prisma db push

Step 4: Model Definitions

With the Prisma schema in place, I defined my User and Account models, specifying attributes like email, name, and more. These models form the backbone of our authentication system, enabling us to manage user data and authentication details.

This code sets up the prisma client and ensures that there's only one instance of it throughout your application, which is a good practice to manage resources and improve performance.

import { PrismaClient } from '@prisma/client';

const client = globalThis.prisma || new PrismaClient();

if (process.env.NODE_ENV === 'production') {
  globalThis.prisma = client;
}

export default client;

Step 5: Creating a Powerhouse Configuration

At the heart of our authentication adventure lies a configuration file that orchestrates the entire authentication symphony. Importing NextAuth.js and its necessary components, we set the stage for a seamless authentication experience.

I created an auth folder within the api directory in the app folder. Inside this auth folder, I added a catch-all route named [...nextauth] and route.jsx. This route became the entry point for our authentication magic, where NextAuth.js seamlessly handled authentication provider interactions.

// Import necessary dependencies
import NextAuth from 'next-auth/next';
import { PrismaAdapter } from '@next-auth/prisma-adapter';
import CredentialsProvider from 'next-auth/providers/credentials';
import GoogleProvider from 'next-auth/providers/google';
import GithubProvider from 'next-auth/providers/github';
import prisma from '@/app/libs/prismadb'; // Import Prisma client instance
import bcrypt from 'bcrypt'; // Import bcrypt for password hashing

// Configuration options for NextAuth
export const authOptions = {
    // Use Prisma as the adapter for NextAuth
    adapter: PrismaAdapter(prisma),
    providers: [
        // GitHub authentication provider
        GithubProvider({
            clientId: process.env.GITHUB_ID,
            clientSecret: process.env.GITHUB_SECRET
        }),
        // Google authentication provider
        GoogleProvider({
            clientId: process.env.GOOGLE_ID,
            clientSecret: process.env.GOOGLE_SECRET
        }),
        // Custom Credentials provider for email/password authentication
        CredentialsProvider({
            name: "credentials",
            credentials: {
                email: { label: "Email", type: "text", placeholder: "Enter your Email" },
                password: { label: "Password", type: "password", placeholder: "Enter your password" },
                username: { label: "Username", type: "text", placeholder: "Enter your username" },
            },
            async authorize(credentials) {
                // Check if email and password are provided
                if (!credentials.email || !credentials.password) {
                    throw new Error('Please enter an email and password');
                }

                // Find the user based on the provided email
                const user = await prisma.user.findUnique({
                    where: {
                        email: credentials?.email
                    }
                });

                // If no user is found or no hashed password exists, throw an error
                if (!user || !user?.hashedPassword) {
                    throw new Error("No User Found");
                }

                // Compare provided password with hashed password
                const passwordMatch = await bcrypt.compare(credentials?.password, user?.hashedPassword);
                if (!passwordMatch) {
                    throw new Error("Password does not match");
                }

                return user;
            }
        })
    ],
    // Callbacks for token and session handling
    callbacks: {
        // Callback to update JWT token during session update
        async jwt({ token, user, session, trigger }) {
            if (trigger === "update" && session.name) {
                token.name = session.name;

                // Update the user's name in the database
                await prisma.user.update({
                    where: {
                        id: token.id
                    },
                    data: {
                        name: token.name
                    }
                });
            }

            // Add user ID and address to the token
            if (user) {
                return {
                    ...token,
                    id: user?.id,
                    address: user?.address
                };
            }
            return token;
        },
        // Callback to update session with user data
        async session({ token, user, session }) {
            // Add user ID, address, and name to the session
            return {
                ...session,
                user: {
                    ...session.user,
                    id: token.id,
                    address: token.address,
                    name: token.name
                }
            };
        },
    },
    // Global secret and session strategy
    secret: process.env.SECRET,
    session: {
        strategy: "jwt"
    },
    // Enable debugging in development environment
    debug: process.env.NODE_ENV === "development"
};

// Create a NextAuth handler using the configured options
const handler = NextAuth(authOptions);

// Export the handler for both GET and POST requests
export { handler as GET, handler as POST };

Make sure to replace the placeholders such as process.env.GITHUB_ID, process.env.GITHUB_SECRET, process.env.GOOGLE_ID, and process.env.GOOGLE_SECRET with your actual OAuth credentials. Also, ensure that the prisma instance is correctly imported and configured.

here's the content of your .env file with hidden values:

# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings

DATABASE_URL="your-mongodb-connection-string"
NODE_ENV="development"
SECRET="your-secret-key"
GITHUB_ID="your-github-id"
GITHUB_SECRET="your-github-secret"
GOOGLE_ID="your-google-id"
GOOGLE_SECRET="your-google-secret"

Replace the placeholders (your-mongodb-connection-string, your-secret-key, etc.) with your actual values. Remember to keep this file private and not commit it to version control

First, integrate the AuthProvider into your layout component (RootLayout) like this:

import AuthProvider from './context/AuthContext';
import './globals.css';
import { Inter } from 'next/font/google';

const inter = Inter({ subsets: ['latin'] });

export const metadata = {
  title: 'Next Auth',
  description: 'Studying next auth credentials',
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        {/* ... */}
        <style>{inter.styles}</style>
      </head>
      <body className={inter.className}>
        <AuthProvider>
          {children}
        </AuthProvider>
      </body>
    </html>
  );
}
// context/AuthContext.js

import { SessionProvider } from 'next-auth/react';
import { Toaster } from 'react-hot-toast';

/**
 * The AuthProvider component wraps the application with session
 * management provided by NextAuth.js. It ensures that the session
 * state is available to all components throughout the app.
 * The Toaster component from react-hot-toast is also added to display
 * toast notifications.
 */
function AuthProvider({ children }) {
  return (
    <>
      <SessionProvider>{children}</SessionProvider>
      <Toaster />
    </>
  );
}

export default AuthProvider;

Step 6.1: Register Page

A successful authentication system is incomplete without a user-friendly registration process. On our register page, we'll design a form that captures essential user details such as email, password, and username. By integrating this page with NextAuth.js, we empower users to create accounts effortlessly.

This code snippet is an API route handler that handles user registration. It validates input data, checks for existing users, hashes passwords, and creates a new user in the database. Make sure you have the necessary dependencies and configurations in place for this code to work as intended.

import bcrypt from 'bcrypt'; // Import bcrypt for password hashing
import prisma from '../../libs/prismadb'; // Import Prisma for database interaction
import { NextResponse } from 'next/server'; // Import NextResponse for handling API responses

export async function POST(request) {
    const data = await request.json(); // Parse JSON data from the request body
    const { name, email, address, password } = data; // Destructure user data

    // Check if any required fields are missing
    if (!name || !email || !address || !password) {
        return new NextResponse('Missing fields', { status: 400 });
    }

    // Query the database to check if the user with the given email already exists
    const exist = await prisma.user.findUnique({
        where: {
            email
        }
    });

    // If a user with the same email exists, throw an error
    if (exist) {
        throw new Error('Email already exists');
    }

    // Hash the provided password using bcrypt with a cost factor of 10
    const hashedPassword = await bcrypt.hash(password, 10);

    // Create a new user record in the database with hashed password
    const user = await prisma.user.create({
        data: {
            name,
            email,
            address,
            hashedPassword
        }
    });

    // Return a JSON response containing the user data
    return NextResponse.json(user);
}

The front-end part is a React component that displays a form where users can input their registration details, such as email, address, username, and password.

"use client"
import axios from 'axios';
import { useRouter } from 'next/navigation';
import { useState, useEffect } from 'react';
import toast from 'react-hot-toast';
import {  useSession} from 'next-auth/react'

export default function RegisterPage() {
  const router = useRouter()
  const [data, setData] = useState({
    name: '',
    email: '',
    password: '',
    address: ''
  })
  const session = useSession()
  const registerUser = async (e) => {
    e.preventDefault()
    axios.post('/api/register', data)
      .then(() => {
        toast.success("User has been registered")
        router.push('/login')
      })
      .catch((error) => {
        // console.log(error)
        toast.error("Something went  wrong")
      })

  }

  return (
    <>

      <div className="flex min-h-full flex-1 flex-col justify-center px-6 py-12 lg:px-8">
        <div className="sm:mx-auto sm:w-full sm:max-w-sm">
          <img
            className="mx-auto h-10 w-auto"
            src="https://w7.pngwing.com/pngs/937/386/png-transparent-registered-trademark-symbol-copyright-copyright-game-text-trademark-thumbnail.png"
            alt="Your Company"
          />
          <h2 className="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
            Register For the Account
          </h2>
        </div>

        <div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
          <form className="space-y-6" onSubmit={registerUser}>
            <div>
              <label htmlFor="email" className="block text-sm font-medium leading-6 text-gray-900">
                Email address
              </label>
              <div className="mt-2">
                <input
                  id="email"
                  name="email"
                  type="email"
                  value={data.email}
                  onChange={e => setData({ ...data, email: e.target.value })}
                  required
                  className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                />
              </div>
            </div>
            <div>
              <label htmlFor="address" className="block text-sm font-medium leading-6 text-gray-900">
                Address
              </label>
              <div className="mt-2">
                <input
                  id="address"
                  name="address"
                  type="address"
                  value={data.address}
                  onChange={e => setData({ ...data, address: e.target.value })}
                  required
                  className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                />
              </div>
            </div>
            <div>
              <label htmlFor="name" className="block text-sm font-medium leading-6 text-gray-900">
                Username
              </label>
              <div className="mt-2">
                <input
                  id="name"
                  name="name"
                  type="text"
                  value={data.name}
                  onChange={e => setData({ ...data, name: e.target.value })}
                  required
                  className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                />
              </div>
            </div>

            <div>
              <div className="flex items-center justify-between">
                <label htmlFor="password" className="block text-sm font-medium leading-6 text-gray-900">
                  Password
                </label>

              </div>
              <div className="mt-2">
                <input
                  id="password"
                  name="password"
                  type="password"
                  value={data.password}
                  onChange={e => setData({ ...data, password: e.target.value })}
                  required
                  className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                />
              </div>
            </div>

            <div>
              <button
                type="submit"
                className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
              >
                Register
              </button>
            </div>
          </form>
        </div>
      </div>
    </>
  )
}

Step 6.2: Login Page

This code provides a complete login UI with form inputs for email and password, as well as options to sign in using Google and GitHub accounts. The useSession hook is used to check the authentication status and automatically redirect authenticated users to the main page. The signIn function is used to handle the login process using the credentials provider and OAuth2 providers (Google and GitHub). Make sure you have the necessary dependencies and configurations in place for this code to work as intended.

import axios from 'axios'; // HTTP client library
import { useRouter } from 'next/navigation'; // Router for navigation
import { useState, useEffect } from 'react'; // State management
import toast from 'react-hot-toast'; // Toast notifications
import { signIn, useSession } from 'next-auth/react'; // NextAuth session management

export default function LoginPage() {
  const router = useRouter(); // Router instance
  const session = useSession(); // Session state from NextAuth
  const [data, setData] = useState({
    email: '',
    password: '',
  });

  // Function to handle user login
  const loginUser = async (e) => {
    e.preventDefault(); // Prevent form submission
    signIn('credentials', {
      ...data,
      redirect: false,
    }) // Sign in using credentials provider
      .then((callback) => {
        if (callback?.error) {
          toast.error(callback.error);
        }

        if (callback?.ok && !callback?.error) {
          toast.success('Logged in successfully!');
          router.push('/');
        }
      });
  };

  // Automatically redirect if the user is already authenticated
  useEffect(() => {
    if (session?.status === 'authenticated') {
      router.push('/');
    }
  }, [session]);

  return (
    <>
      {/* Login form */}
      <div className="flex min-h-full flex-1 flex-col justify-center px-6 py-12 lg:px-8">
        {/* ... (Form header) */}
        <div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
          <form className="space-y-6" onSubmit={loginUser}>
            {/* Email input */}
            <div>
              <label htmlFor="email" className="block text-sm font-medium leading-6 text-gray-900">
                Email address
              </label>
              <div className="mt-2">
                <input
                  id="email"
                  name="email"
                  type="email"
                  value={data.email}
                  onChange={(e) => setData({ ...data, email: e.target.value })}
                  required
                  className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                />
              </div>
            </div>
            {/* Password input */}
            <div>
              {/* ... (Forgot password link) */}
              <div className="mt-2">
                <input
                  id="password"
                  name="password"
                  type="password"
                  value={data.password}
                  onChange={(e) => setData({ ...data, password: e.target.value })}
                  required
                  className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                />
              </div>
            </div>
            {/* Sign in button */}
            <div>
              <button
                type="submit"
                className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
              >
                Sign in
              </button>
            </div>
          </form>
          {/* OAuth2 sign-in options */}
          <div onClick={() => signIn('google')} className="bg-blue-800 text-gray-100 hover:text-white shadow font-bold text-sm py-3 px-4 rounded flex justify-start items-center cursor-pointer w-96">
            {/* ... (Google sign-in SVG) */}
            <span className="border-l border-blue-500 h-6 w-1 block"></span>
            <span className="pl-3">Sign In with Google</span>
          </div>
          <div onClick={() => signIn('github')} className="bg-gray-900 text-gray-100 hover:text-white shadow font-bold text-sm py-3 px-4 rounded flex justify-start items-center cursor-pointer w-96 mt-2">
            {/* ... (GitHub sign-in SVG) */}
            <span className="border-l border-gray-800 h-6 w-1 block mr-1"></span>
            <span className="pl-3">Sign In with GitHub</span>
          </div>
        </div>
      </div>
    </>
  );
}

This code includes the simplified UI for the home page. The user's name is displayed dynamically based on the session state. Users with a session can also update their names.

Sign Out Functionality: The signOut function is imported from the next-auth/react library. It allows users to log out from their authenticated session. When the "Log out" button is clicked, the signOut function is invoked, and the user's session is ended.

Update User Name Functionality: Users with an active session can update their names. The new name is entered in an input field, and when the "Update" button is clicked, the update function is called. This function uses the update method from the session object to update the user's name. The user's session is automatically refreshed to reflect the updated name.

The update function is provided by the useSession hook from the next-auth/react library. It allows you to update specific properties of the user's session, such as the user's name in this case. After updating the name, the user's session is automatically refreshed to reflect the changes. These changes are reflected database also (refer to callback function) in catch all routes [...nextauth] inside auth of api folder

import { useState } from 'react';
import { Dialog } from '@headlessui/react';
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline';
import { useSession } from 'next-auth/react';
import { signOut } from 'next-auth/react';
import Link from 'next/link';

const navigation = [
  { name: 'Home', href: '/' },
  { name: 'Blog', href: '/' },
  { name: 'Contact Me', href: '/' },
];

export default function Home() {
  const { data: session, update } = useSession();
  const [updateName, setUpdateName] = useState('');
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);

  const handleUpdateName = () => {
    if (updateName) {
      update({ name: updateName });
    }
  };

  return (
    <div className="bg-white">
      <header className="absolute inset-x-0 top-0 z-50">
        {/* ... (Header content) */}
      </header>

      <div className="relative isolate px-6 pt-14 lg:px-8">
        {/* ... (Background elements) */}
        <div className="mx-auto max-w-2xl py-16 sm:py-20 lg:py-32">
          <div className="text-center">
            <h1 className="text-4xl font-bold tracking-tight text-gray-900 sm:text-6xl">
              Welcome {session ? session.user.name : 'to NextAuth Project'}
            </h1>
            {session && (
              <div className="w-full flex items-center justify-center mt-5">
                <div className="flex items-center w-auto border-b border-teal-500 py-2">
                  <input
                    className="appearance-none bg-transparent border-none w-full text-gray-700 mr-3 py-1 px-2 leading-tight focus:outline-none"
                    type="text"
                    required
                    placeholder="Update your Name"
                    aria-label="Full name"
                    value={updateName || session.user.name}
                    onChange={(e) => setUpdateName(e.target.value)}
                  />
                  <button
                    onClick={handleUpdateName}
                    className="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
                  >
                    Update
                  </button>
                </div>
              </div>
            )}
            {/* ... (Other content) */}
          </div>
        </div>
        {/* ... (Background elements) */}
      </div>
    </div>
  );
}

Certainly! You can find the GitHub repository for the project we discussed here: NextAuth.js Authentication Project

To see the project in action, check out this video walkthrough: Video Link

Feel free to explore the code, review the implementation details, and reach out if you have any questions or doubts. This repository serves as a reference for the authentication project we discussed, and you can use it to better understand the concepts and functionalities we covered. If you have any queries or need further assistance, don't hesitate to reach out through the provided contact channels. Happy coding!

Project Conclusion:

In this project, we embarked on an exciting journey to enhance user authentication within a mini-testing environment using NextAuth.js. Leveraging the power of NextAuth.js, we seamlessly integrated authentication with Google, and GitHub, and even introduced a custom Credentials provider, granting users the flexibility to use their credentials. This project showcases the seamless implementation of authentication mechanisms, enabling users to securely access the application using different authentication providers.

NextAuth.js is a powerful authentication library that simplifies the process of adding authentication to Next.js applications. It offers a wide range of authentication providers, including social logins like Google, GitHub, and more, as well as custom credentials authentication. NextAuth.js handles authentication flows, session management, and access to user data with ease. It also provides hooks and functions for easy integration into components, making user authentication a breeze.

Connect with Me:

If you have any questions about this project or would like to discuss potential collaboration opportunities, feel free to reach out to me. I'm always excited to connect with fellow professionals in the tech industry and explore ways to work together. You can reach me through the following channels:

I'm here to help and collaborate, so don't hesitate to get in touch. Let's connect and explore the exciting possibilities in the world of technology together!