Table of Contents

Beginner's Guide to Next.js Authentication with NextAuth.js and MongoDB (2025 Edition)

Introduction: What is Authentication and Why Do You Need It?

Ever visited a website that remembers who you are and shows your personal information? That's authentication in action! Authentication is like the digital version of checking someone's ID - it verifies that users are who they claim to be.

Adding authentication to your Next.js website is important because it:

  • Keeps user information safe from unauthorized access
  • Allows you to create personalized experiences for each user
  • Protects private sections of your website
  • Builds trust with your visitors who know their data is secure
  • Helps comply with data protection regulations

NextAuth.js makes adding authentication to your Next.js app super easy, even if you're a beginner. It works with popular login methods like Google and GitHub, and you can also use traditional email/password login. Best of all, we'll connect it to MongoDB, a beginner-friendly database that's perfect for web applications!

Authentication Terminology for Beginners

Before diving into code, let's understand some key terms:

Authentication vs. Authorization:

  • Authentication: Verifying who a user is (login)
  • Authorization: Determining what a user can access (permissions)

OAuth: A standard protocol that allows users to log in to your app using accounts from services like Google or GitHub without sharing their passwords with you.

JWT (JSON Web Tokens): Small, secure tokens that contain user information. Think of them like digital wristbands at an event that show you've already checked in.

Session: A way to remember a user between page visits. Like a conversation that continues even if you pause and come back later.

Cookies: Small pieces of data stored in a user's browser that help maintain their login state.

Hashing: A one-way process to securely store passwords by converting them to scrambled text that can't be reversed.

What You'll Build in This Tutorial

By the end of this beginner-friendly guide, you'll create a Next.js application with:

  1. A login page with social login buttons (Google and GitHub)
  2. Secure email/password login that properly protects passwords
  3. Protected pages that only logged-in users can access
  4. User profiles showing information from their social accounts
  5. Data storage in MongoDB, a popular and free database
  6. Session management that keeps users logged in

![Authentication Flow Example]

What You'll Need Before Starting

  • Basic knowledge of JavaScript and React (you don't need to be an expert!)
  • Node.js installed on your computer (download from nodejs.org)
  • A code editor like Visual Studio Code (it's free!)
  • A free MongoDB Atlas account (we'll show you how to set this up)
  • A free GitHub or Google account for setting up social login

Don't worry if you're new to some of these technologies - we'll explain each step clearly!

Step 1: Understanding How NextAuth.js Works

Before writing any code, let's understand how NextAuth.js handles authentication:

  1. User Starts Login: User clicks "Sign in with Google" or enters email/password
  2. Authentication Request: NextAuth redirects to the provider (Google, GitHub) or checks credentials
  3. Provider Response: After authentication, the provider sends information back to NextAuth
  4. Session Creation: NextAuth creates a secure session and JWT (JSON Web Token)
  5. User is Logged In: NextAuth sets cookies to maintain the session

The beauty of NextAuth is that it handles all these steps for you - you just need to set up the configuration!

Authentication Flow Types

NextAuth supports two main ways to handle sessions:

JWT Strategy (default):

  • Stores user information in an encrypted token in the browser
  • Works without a database (great for simple projects)
  • Fast and stateless (no server lookups needed)

Database Strategy:

  • Stores sessions in your MongoDB database
  • Gives you more control over user sessions
  • Better for more complex applications

We'll use the JWT strategy for simplicity but explain how to switch if needed.

Step 2: Creating Your Next.js Project

Let's start by creating a new Next.js application. Open your terminal (Command Prompt or Terminal app) and run:

Bash
1npx create-next-app@latest my-auth-app
2cd my-auth-app

This command creates a new Next.js project with the latest features. When prompted, select the default options.

Tip: If you're completely new to Next.js, take a few minutes to explore the generated files. The main folders are:

  • /app: Contains your pages and components
  • /public: For static files like images
  • Next.js 13+ uses the App Router, which might look different from older tutorials!

Step 3: Installing NextAuth.js

Now let's add the authentication library. In your terminal, run:

Bash
npm install next-auth@latest @types/next-auth

This installs NextAuth.js and its TypeScript types. NextAuth will handle all the complex authentication logic for us!

Step 4: Understanding Environment Variables and Security

When building apps with authentication, keeping secrets secure is critical. Let's understand why:

What are environment variables? Environment variables are like secret notes your application can read but aren't visible in your code. They're perfect for storing sensitive information like API keys.

Why use them?

  • They keep sensitive data out of your code repository
  • They let you use different values in development and production
  • They're a standard practice for security

Let's set up your environment variables:

  1. Create a file named .env.local in your project's main folder
  2. Add the following template:
Plain Text
1NEXTAUTH_URL=http://localhost:3000
2NEXTAUTH_SECRET=your_secure_key_here
3MONGODB_URI=your_mongodb_connection_string
4GOOGLE_CLIENT_ID=your_google_id
5GOOGLE_CLIENT_SECRET=your_google_secret
6GITHUB_CLIENT_ID=your_github_id
7GITHUB_CLIENT_SECRET=your_github_secret

The NEXTAUTH_SECRET is particularly important - it's used to encrypt your tokens. Generate a secure random string by:

On Mac/Linux: Open Terminal and type:

Bash
openssl rand -base64 32

On Windows: Open PowerShell and type:

Powershell
[Convert]::ToBase64String([System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32))

Security Tip: Never commit your .env.local file to Git! Add it to your .gitignore file to prevent accidentally sharing your secrets.

Step 5: Understanding OAuth and Setting Up Providers

What is OAuth and Why Use It?

OAuth allows users to log in using their existing accounts from services like Google or GitHub. Benefits include:

  • Users don't need to create yet another password
  • You don't have to handle password security
  • Higher trust as users authenticate with brands they recognize
  • Access to profile information from these providers

Now let's set up two popular OAuth providers:

Setting Up Google Authentication

  1. Go to the Google Cloud Console
  2. Create a new project by clicking the project dropdown at the top and selecting "New Project"
  3. Give your project a name like "My Auth App" and click "Create"
  4. Once created, select it and go to "APIs & Services" > "Credentials"
  5. Click "Configure Consent Screen" (select "External" if prompted)
  6. Fill in required information (app name, support email)
  7. Add your email as a test user and complete the form
  8. Return to Credentials, click "Create Credentials" > "OAuth client ID"
  9. Select "Web Application" as the application type
  10. Add http://localhost:3000 under "Authorized JavaScript origins"
  11. Add http://localhost:3000/api/auth/callback/google under "Authorized redirect URIs"
  12. Copy the provided Client ID and Client Secret to your .env.local file

Tip: The redirect URI is where Google will send users after they log in. This must match exactly what NextAuth expects.

Setting Up GitHub Authentication

  1. Go to GitHub Developer Settings
  2. Click "New OAuth App"
  3. Fill in:
    • Application name: "My Next.js Auth App"
    • Homepage URL: http://localhost:3000
    • Authorization callback URL: http://localhost:3000/api/auth/callback/github
  4. Copy the Client ID and generate a Client Secret
  5. Add these to your .env.local file

Step 6: Understanding NoSQL Databases and MongoDB

What is MongoDB and Why Use It?

MongoDB is a "NoSQL" database that stores data in flexible, JSON-like documents. It's different from traditional SQL databases in several ways:

  • Flexible Schema: You can add fields to documents without redefining the entire database structure
  • JSON-like Documents: Data is stored in a format that's natural for JavaScript developers
  • Easy Scaling: Great for applications that might grow quickly
  • Perfect for User Data: Excellent for storing user profiles with varying information

For our authentication system, MongoDB will store:

  • User account information
  • Provider connections (links to Google/GitHub accounts)
  • Sessions (if you choose database sessions instead of JWT)

Setting Up MongoDB Atlas

MongoDB Atlas provides a free cloud database perfect for learning and small projects:

  1. Go to MongoDB Atlas and create a free account
  2. After signing up, create a new cluster (select the FREE tier)
  3. Choose a cloud provider and region closest to you
  4. Click "Create Cluster" (this takes a few minutes)
  5. Once ready, click "Connect"
  6. Create a database user with a secure password
  7. Under "Where would you like to connect from?" add 0.0.0.0/0 to allow access from anywhere
  8. Choose "Connect your application" and copy the connection string
  9. Replace <password> with your database user's password
  10. Add the connection string to your .env.local file as MONGODB_URI

Understanding Mongoose

Mongoose is a library that makes working with MongoDB easier in Node.js:

  • It provides a schema-based solution to model your data
  • It offers built-in type casting and validation
  • It handles the connection to MongoDB for you

Install MongoDB packages:

Bash
npm install mongodb mongoose @auth/mongodb-adapter

Step 7: Understanding Models in Mongoose

Before writing code, let's understand what database models are:

Models are like blueprints for your data. They define:

  • What fields your data has (name, email, password)
  • What types these fields should be (string, number, date)
  • Which fields are required and which are optional
  • Any validation rules (minimum length, format)

For our authentication system, we need models for:

  • User: Stores basic user information (name, email, image)
  • Account: Links users to their social providers
  • Session: Stores active login sessions
  • Verification Token: For email verification

Here's a simplified look at our User model:

JavaScript
1// This defines what user data looks like in our database
2const UserSchema = new mongoose.Schema({
3  name: String,
4  email: {
5    type: String,
6    unique: true  // No duplicate emails allowed
7  },
8  password: String,
9  image: String
10});

We'll implement these models in the following sections.

Step 8: Setting Up Database Connection

Now let's create the files to connect to MongoDB:

  1. Create a lib folder in your project root
  2. Create lib/mongodb.js file:
JavaScript
1import mongoose from 'mongoose';
2
3const MONGODB_URI = process.env.MONGODB_URI;
4
5if (!MONGODB_URI) {
6  throw new Error('Please define the MONGODB_URI environment variable');
7}
8
9// This prevents multiple connections during development
10let cached = global.mongoose;
11
12if (!cached) {
13  cached = global.mongoose = { conn: null, promise: null };
14}
15
16async function dbConnect() {
17  if (cached.conn) {
18    return cached.conn;
19  }
20
21  if (!cached.promise) {
22    cached.promise = mongoose.connect(MONGODB_URI).then(mongoose => {
23      return mongoose;
24    });
25  }
26  
27  cached.conn = await cached.promise;
28  return cached.conn;
29}
30
31export default dbConnect;

This code establishes a connection to MongoDB and caches it to prevent creating multiple connections.

  1. Create a separate client for the NextAuth adapter in lib/mongodb-client.js:
JavaScript
1import { MongoClient } from 'mongodb';
2
3const uri = process.env.MONGODB_URI;
4const options = {};
5
6let client;
7let clientPromise;
8
9if (!process.env.MONGODB_URI) {
10  throw new Error('Please add your MongoDB URI to .env.local');
11}
12
13// In development, use a global variable to preserve the connection across hot reloads
14if (process.env.NODE_ENV === 'development') {
15  if (!global._mongoClientPromise) {
16    client = new MongoClient(uri, options);
17    global._mongoClientPromise = client.connect();
18  }
19  clientPromise = global._mongoClientPromise;
20} else {
21  // In production, create a new client
22  client = new MongoClient(uri, options);
23  clientPromise = client.connect();
24}
25
26export default clientPromise;

Tip for Beginners: The difference between these two files might be confusing. The first uses Mongoose (a higher-level library) for our custom code, while the second uses the raw MongoDB driver for NextAuth's adapter.

Step 9: Creating User Models

Now let's create our database models. Create a models folder and add these files:

  1. models/User.js:
JavaScript
1import mongoose from 'mongoose';
2
3// Only create the model if it doesn't exist already
4const UserSchema = new mongoose.Schema({
5  name: String,
6  email: {
7    type: String,
8    unique: true,
9    sparse: true,
10  },
11  emailVerified: Date,
12  image: String,
13  password: String,
14});
15
16export default mongoose.models.User || mongoose.model('User', UserSchema);
  1. models/Account.js:
JavaScript
1import mongoose from 'mongoose';
2
3const AccountSchema = new mongoose.Schema({
4  userId: {
5    type: mongoose.Schema.Types.ObjectId,
6    ref: 'User',
7  },
8  type: String,
9  provider: String,
10  providerAccountId: String,
11  refresh_token: String,
12  access_token: String,
13  expires_at: Number,
14  token_type: String,
15  scope: String,
16  id_token: String,
17  session_state: String,
18});
19
20// Make sure we don't connect the same account twice
21AccountSchema.index({ provider: 1, providerAccountId: 1 }, { unique: true });
22
23export default mongoose.models.Account || mongoose.model('Account', AccountSchema);
  1. Create similar models for Session.js and VerificationToken.js

Understanding Mongoose Models: These models define the structure of our data. The mongoose.models.X || mongoose.model('X', Schema) pattern prevents errors when the app hot reloads during development.

Step 10: Understanding JWT vs. Database Sessions

Before configuring NextAuth, let's understand the two session strategies:

JWT Sessions (Default)

  • How it works: User info is stored in an encrypted token in the browser
  • Pros: Faster (no database lookups), works without a database, simpler
  • Cons: Can't revoke sessions immediately, limited storage

Database Sessions

  • How it works: Only a session ID is stored in the browser; all data is in the database
  • Pros: Can revoke sessions instantly, can store more user data
  • Cons: Requires database lookups on each request, slightly more complex

For beginners, JWT sessions are usually simpler, but knowing the difference helps you make an informed choice.

Step 11: Configuring NextAuth API Route

Now let's set up the core of our authentication system. Create a file at app/api/auth/[...nextauth]/route.js:

JavaScript
1import NextAuth from "next-auth";
2import GoogleProvider from "next-auth/providers/google";
3import GitHubProvider from "next-auth/providers/github";
4import CredentialsProvider from "next-auth/providers/credentials";
5import { MongoDBAdapter } from "@auth/mongodb-adapter";
6import { compare } from "bcryptjs";
7import clientPromise from "@/lib/mongodb-client";
8import User from "@/models/User";
9import dbConnect from "@/lib/mongodb";
10
11// This is where we configure our authentication system
12export const authOptions = {
13  // Use MongoDB adapter to store user data
14  adapter: MongoDBAdapter(clientPromise),
15  
16  // Configure authentication providers
17  providers: [
18    // Google login
19    GoogleProvider({
20      clientId: process.env.GOOGLE_CLIENT_ID,
21      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
22    }),
23    
24    // GitHub login
25    GitHubProvider({
26      clientId: process.env.GITHUB_CLIENT_ID,
27      clientSecret: process.env.GITHUB_CLIENT_SECRET,
28    }),
29    
30    // Email/password login
31    CredentialsProvider({
32      name: "Email",
33      credentials: {
34        email: { label: "Email", type: "email" },
35        password: { label: "Password", type: "password" }
36      },
37      async authorize(credentials) {
38        if (!credentials?.email || !credentials?.password) {
39          return null;
40        }
41        
42        // Connect to database
43        await dbConnect();
44        
45        // Find user by email
46        const user = await User.findOne({ email: credentials.email });
47        
48        if (!user || !user.password) {
49          return null;
50        }
51        
52        // Check if password matches
53        const isPasswordValid = await compare(
54          credentials.password,
55          user.password
56        );
57        
58        if (!isPasswordValid) {
59          return null;
60        }
61        
62        // Return user object if authentication succeeds
63        return {
64          id: user._id.toString(),
65          email: user.email,
66          name: user.name,
67          image: user.image,
68        };
69      }
70    }),
71  ],
72  
73  // Session configuration
74  session: {
75    strategy: "jwt", // Use JWT for sessions
76    maxAge: 30 * 24 * 60 * 60, // 30 days
77  },
78  
79  // Callbacks to customize behavior
80  callbacks: {
81    async session({ session, token }) {
82      if (token && session.user) {
83        session.user.id = token.sub;
84        // You can add custom fields here
85      }
86      return session;
87    },
88  },
89  
90  // Pages for custom authentication flow
91  pages: {
92    signIn: "/auth/signin",
93    error: "/auth/error",
94  },
95};
96
97// This creates the API route handler
98const handler = NextAuth(authOptions);
99export { handler as GET, handler as POST };

Understanding This Code: This is the heart of your authentication system. It:

  • Sets up providers (Google, GitHub, email/password)
  • Configures session handling
  • Defines callback functions for customizing behavior
  • Specifies custom pages for the authentication flow

Step 12: Setting Up the Session Provider

For authentication to work across your app, you need to wrap it with a session provider. Create app/providers.jsx:

React JSX
1"use client";
2
3import { SessionProvider } from "next-auth/react";
4
5export function Providers({ children }) {
6  return <SessionProvider>{children}</SessionProvider>;
7}

Then update your app/layout.jsx:

React JSX
1import { Providers } from "./providers";
2import "./globals.css";
3
4export const metadata = {
5  title: "My Auth App",
6  description: "A Next.js app with authentication",
7};
8
9export default function RootLayout({ children }) {
10  return (
11    <html lang="en">
12      <body>
13        <Providers>{children}</Providers>
14      </body>
15    </html>
16  );
17}

This ensures the authentication state is available throughout your application.

Tip: The "use client" directive is important here because SessionProvider uses browser APIs that aren't available during server rendering.

Step 13: Creating a Sign-In Page

Let's create a simple but attractive sign-in page. Create a file at app/auth/signin/page.jsx:

React JSX
1"use client";
2
3import { useState, useEffect } from "react";
4import { signIn, getProviders } from "next-auth/react";
5import { useRouter } from "next/navigation";
6
7export default function SignIn() {
8  const [email, setEmail] = useState("");
9  const [password, setPassword] = useState("");
10  const [error, setError] = useState("");
11  const [providers, setProviders] = useState(null);
12  const router = useRouter();
13
14  // Load available authentication providers
15  useEffect(() => {
16    const loadProviders = async () => {
17      const providers = await getProviders();
18      setProviders(providers);
19    };
20    loadProviders();
21  }, []);
22
23  // Handle email/password login
24  const handleEmailSignIn = async (e) => {
25    e.preventDefault();
26    setError("");
27
28    try {
29      const result = await signIn("credentials", {
30        redirect: false,
31        email,
32        password,
33      });
34
35      if (result?.error) {
36        setError("Invalid email or password");
37      } else {
38        router.push("/dashboard");
39      }
40    } catch (error) {
41      setError("An unexpected error occurred");
42    }
43  };
44
45  // If providers haven't loaded yet
46  if (!providers) {
47    return <div>Loading...</div>;
48  }
49
50  return (
51    <div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4">
52      <div className="max-w-md w-full space-y-8 bg-white p-8 rounded-xl shadow-lg">
53        <div className="text-center">
54          <h1 className="text-3xl font-bold text-gray-900">Sign In</h1>
55          <p className="mt-2 text-gray-600">Access your account</p>
56        </div>
57        
58        {error && (
59          <div className="bg-red-50 text-red-500 p-3 rounded-lg text-sm">
60            {error}
61          </div>
62        )}
63
64        {/* Email/Password Form */}
65        <form className="mt-8 space-y-6" onSubmit={handleEmailSignIn}>
66          <div>
67            <label htmlFor="email" className="block text-sm font-medium text-gray-700">
68              Email address
69            </label>
70            <input
71              id="email"
72              name="email"
73              type="email"
74              required
75              value={email}
76              onChange={(e) => setEmail(e.target.value)}
77              className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md"
78            />
79          </div>
80          
81          <div>
82            <label htmlFor="password" className="block text-sm font-medium text-gray-700">
83              Password
84            </label>
85            <input
86              id="password"
87              name="password"
88              type="password"
89              required
90              value={password}
91              onChange={(e) => setPassword(e.target.value)}
92              className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md"
93            />
94          </div>
95
96          <button
97            type="submit"
98            className="w-full py-2 px-4 border border-transparent rounded-md text-white bg-indigo-600 hover:bg-indigo-700"
99          >
100            Sign in with Email
101          </button>
102        </form>
103
104        {/* Divider */}
105        <div className="mt-6">
106          <div className="relative">
107            <div className="absolute inset-0 flex items-center">
108              <div className="w-full border-t border-gray-300" />
109            </div>
110            <div className="relative flex justify-center text-sm">
111              <span className="px-2 bg-white text-gray-500">Or continue with</span>
112            </div>
113          </div>
114
115          {/* Social Login Buttons */}
116          <div className="mt-6 grid grid-cols-2 gap-3">
117            {providers && 
118              Object.values(providers)
119                .filter((provider) => provider.id !== "credentials")
120                .map((provider) => (
121                <button
122                  key={provider.id}
123                  onClick={() => signIn(provider.id, { callbackUrl: "/dashboard" })}
124                  className="py-2 px-4 border border-gray-300 rounded-md bg-white hover:bg-gray-50"
125                >
126                  Sign in with {provider.name}
127                </button>
128              ))}
129          </div>
130        </div>
131      </div>
132    </div>
133  );
134}

This page provides both email/password login and social login buttons.

UI Tip: This is a basic design using Tailwind CSS (included with Next.js by default). Feel free to customize the appearance to match your app's style!

Step 14: Understanding Route Protection

One of the main reasons for adding authentication is to protect certain routes from unauthorized access. Let's understand how this works in Next.js with the App Router:

Server-Side Protection: This checks if a user is logged in before rendering a page. If not, they are redirected to the login page.

Client-Side Protection: This hides or shows components based on the user's login status.

Let's implement both approaches:

Server-Side Protection

Create a dashboard page at app/dashboard/page.jsx:

React JSX
1import { getServerSession } from "next-auth";
2import { authOptions } from "@/app/api/auth/[...nextauth]/route";
3import { redirect } from "next/navigation";
4
5export default async function Dashboard() {
6  // Get the session on the server
7  const session = await getServerSession(authOptions);
8
9  // If no session exists, redirect to login
10  if (!session) {
11    redirect("/auth/signin");
12  }
13
14  // If we get here, the user is logged in
15  return (
16    <div className="max-w-4xl mx-auto p-6">
17      <h1 className="text-3xl font-bold mb-6">Welcome to your Dashboard</h1>
18      <div className="bg-white shadow rounded-lg p-6">
19        <p>Hello, {session.user?.name || session.user?.email}!</p>
20        <p>This page is protected and only visible if you're logged in.</p>
21      </div>
22    </div>
23  );
24}

Client-Side Protection with useSession

Create a components folder in your project root, then add components/UserProfile.jsx:

React JSX
1"use client";
2
3import { useSession, signOut } from "next-auth/react";
4
5export default function UserProfile() {
6  // Get session on the client side
7  const { data: session, status } = useSession();
8  
9  // Show loading state
10  if (status === "loading") {
11    return <div>Loading user profile...</div>;
12  }
13
14  // Show login prompt if not logged in
15  if (!session) {
16    return <p>Please sign in to view your profile</p>;
17  }
18
19  // Show user info if logged in
20  return (
21    <div>
22      <div className="flex items-center space-x-4">
23        {session.user?.image && (
24          <img 
25            src={session.user.image} 
26            alt="Profile picture"
27            className="w-16 h-16 rounded-full"
28          />
29        )}
30        
31        <div>
32          {session.user?.name && (
33            <h2 className="text-xl font-semibold">{session.user.name}</h2>
34          )}
35          {session.user?.email && (
36            <p>{session.user.email}</p>
37          )}
38          <button
39            onClick={() => signOut({ callbackUrl: "/" })}
40            className="mt-2 text-sm text-red-600 hover:text-red-800"
41          >
42            Sign out
43          </button>
44        </div>
45      </div>
46    </div>
47  );
48}

Add this component to your dashboard:

React JSX
1import { getServerSession } from "next-auth";
2import { authOptions } from "@/app/api/auth/[...nextauth]/route";
3import { redirect } from "next/navigation";
4import UserProfile from "@/components/UserProfile";
5
6export default async function Dashboard() {
7  const session = await getServerSession(authOptions);
8
9  if (!session) {
10    redirect("/auth/signin");
11  }
12
13  return (
14    <div className="max-w-4xl mx-auto p-6">
15      <h1 className="text-3xl font-bold mb-6">Dashboard</h1>
16      <div className="bg-white shadow rounded-lg p-6">
17        <UserProfile />
18        
19        <div className="mt-8">
20          <h2 className="text-xl font-semibold mb-4">Your Secure Content</h2>
21          <p>This content is only visible to authenticated users.</p>
22        </div>
23      </div>
24    </div>
25  );
26}

Understanding the Difference: Server components check authentication during page generation, while client components can respond to login state changes in real-time.

Step 15: Adding Password Registration and Hashing

For email/password login to work, we need to add user registration and secure password storage. First, let's understand password hashing:

What is Password Hashing?

Hashing converts passwords into scrambled strings that can't be reversed:

  • Original password: "mySecurePassword123"
  • Hashed password: "$2a$10$XJFyHN5xnfDUVVB4T7OXA.nVgFDNUy8.YBQoQoHn8NruXGxnAYnW6"

If your database is ever compromised, attackers only see the hashed versions, not actual passwords.

Install bcryptjs for password hashing:

Bash
npm install bcryptjs

Now create a registration page at app/auth/register/page.jsx:

React JSX
1"use client";
2
3import { useState } from "react";
4import { useRouter } from "next/navigation";
5import Link from "next/link";
6
7export default function Register() {
8  const [name, setName] = useState("");
9  const [email, setEmail] = useState("");
10  const [password, setPassword] = useState("");
11  const [error, setError] = useState("");
12  const router = useRouter();
13
14  const handleSubmit = async (e) => {
15    e.preventDefault();
16    setError("");
17
18    try {
19      // Send registration data to our API
20      const response = await fetch("/api/register", {
21        method: "POST",
22        headers: { "Content-Type": "application/json" },
23        body: JSON.stringify({ name, email, password }),
24      });
25
26      const data = await response.json();
27
28      if (!response.ok) {
29        throw new Error(data.message || "Registration failed");
30      }
31
32      // Redirect to sign-in page after successful registration
33      router.push("/auth/signin?registered=true");
34    } catch (error) {
35      setError(error.message);
36    }
37  };
38
39  return (
40    <div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4">
41      <div className="max-w-md w-full space-y-8 bg-white p-8 rounded-xl shadow-lg">
42        <div className="text-center">
43          <h1 className="text-3xl font-bold text-gray-900">Create Account</h1>
44          <p className="mt-2 text-gray-600">Join our community</p>
45        </div>
46        
47        {error && (
48          <div className="bg-red-50 text-red-500 p-3 rounded-lg text-sm">
49            {error}
50          </div>
51        )}
52
53        <form className="mt-8 space-y-6" onSubmit={handleSubmit}>
54          <div>
55            <label htmlFor="name" className="block text-sm font-medium text-gray-700">
56              Name
57            </label>
58            <input
59              id="name"
60              name="name"
61              type="text"
62              required
63              value={name}
64              onChange={(e) => setName(e.target.value)}
65              className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md"
66            />
67          </div>
68          
69          <div>
70            <label htmlFor="email" className="block text-sm font-medium text-gray-700">
71              Email address
72            </label>
73            <input
74              id="email"
75              name="email"
76              type="email"
77              required
78              value={email}
79              onChange={(e) => setEmail(e.target.value)}
80              className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md"
81            />
82          </div>
83          
84          <div>
85            <label htmlFor="password" className="block text-sm font-medium text-gray-700">
86              Password
87            </label>
88            <input
89              id="password"
90              name="password"
91              type="password"
92              required
93              value={password}
94              onChange={(e) => setPassword(e.target.value)}
95              className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md"
96            />
97          </div>
98
99          <button
100            type="submit"
101            className="w-full py-2 px-4 border border-transparent rounded-md text-white bg-indigo-600 hover:bg-indigo-700"
102          >
103            Create Account
104          </button>
105        </form>
106
107        <div className="text-center mt-4">
108          <p className="text-sm text-gray-600">
109            Already have an account?{" "}
110            <Link href="/auth/signin" className="text-indigo-600 hover:text-indigo-500">
111              Sign in
112            </Link>
113          </p>
114        </div>
115      </div>
116    </div>
117  );
118}

Now let's create the API endpoint to handle registration. Create a file at app/api/register/route.js:

JavaScript
1import { hash } from "bcryptjs";
2import User from "@/models/User";
3import dbConnect from "@/lib/mongodb";
4import { NextResponse } from "next/server";
5
6export async function POST(request) {
7  try {
8    const { name, email, password } = await request.json();
9    
10    // Validate the inputs
11    if (!email || !email.includes('@') || !password || password.length < 8) {
12      return NextResponse.json(
13        { message: "Invalid input data" },
14        { status: 400 }
15      );
16    }
17
18    // Connect to the database
19    await dbConnect();
20    
21    // Check if user already exists
22    const existingUser = await User.findOne({ email });
23    if (existingUser) {
24      return NextResponse.json(
25        { message: "User already exists" },
26        { status: 409 }
27      );
28    }
29    
30    // Hash the password (never store plain text passwords!)
31    const hashedPassword = await hash(password, 12);
32    
33    // Create the new user
34    const user = await User.create({
35      name,
36      email,
37      password: hashedPassword,
38    });
39    
40    // Remove password from the response
41    const newUser = {
42      id: user._id.toString(),
43      name: user.name,
44      email: user.email,
45    };
46    
47    return NextResponse.json(
48      { message: "User created successfully", user: newUser },
49      { status: 201 }
50    );
51  } catch (error) {
52    console.error("Registration error:", error);
53    return NextResponse.json(
54      { message: "An error occurred during registration" },
55      { status: 500 }
56    );
57  }
58}

Security Tips:

  • Always hash passwords before storing them
  • Validate user inputs to prevent malicious data
  • Never return sensitive information like password hashes
  • Handle errors gracefully without exposing system details

Step 16: Understanding Middleware for Multiple Protected Routes

If you have many pages that should be protected, creating server-side checks in each file becomes repetitive. Next.js provides a solution called "middleware" that can protect multiple routes at once.

What is Middleware?

Middleware runs before a request is completed, allowing you to:

  • Check if a user is logged in
  • Redirect unauthenticated users
  • Modify request or response headers
  • Apply protection rules across multiple pages

Here's how to set up route protection middleware:

Create a file named middleware.js in your project root:

JavaScript
1import { NextResponse } from "next/server";
2import { getToken } from "next-auth/jwt";
3
4export async function middleware(request) {
5  const { pathname } = request.nextUrl;
6  
7  // Define which paths should be protected
8  const protectedPaths = ["/dashboard", "/profile", "/settings"];
9  const isPathProtected = protectedPaths.some((path) => 
10    pathname.startsWith(path)
11  );
12  
13  // Allow public paths
14  if (!isPathProtected) {
15    return NextResponse.next();
16  }
17  
18  // Check for authentication token
19  const token = await getToken({
20    req: request,
21    secret: process.env.NEXTAUTH_SECRET,
22  });
23  
24  // Redirect to login if no token found
25  if (!token) {
26    const url = new URL(`/auth/signin`, request.url);
27    url.searchParams.set("callbackUrl", pathname);
28    return NextResponse.redirect(url);
29  }
30  
31  // Allow access if authenticated
32  return NextResponse.next();
33}
34
35// Configure which routes use this middleware
36export const config = {
37  matcher: ["/dashboard/:path*", "/profile/:path*", "/settings/:path*"],
38};

Understanding Middleware: Think of middleware as a security guard checking badges before allowing entry to different areas of a building. The guard stands at the entrance and checks everyone before they're allowed to proceed.

Step 17: Creating a User-Friendly Home Page

Let's create a welcoming home page that adapts based on whether the user is signed in.

Create or update app/page.jsx:

React JSX
1import { getServerSession } from "next-auth/auth";
2import { authOptions } from "./api/auth/[...nextauth]/route";
3import Link from "next/link";
4
5export default async function Home() {
6  // Check if user is logged in
7  const session = await getServerSession(authOptions);
8  
9  return (
10    <div className="bg-white">
11      <div className="relative isolate px-6 pt-14 lg:px-8">
12        <div className="mx-auto max-w-2xl py-32 sm:py-48 lg:py-56">
13          <div className="text-center">
14            <h1 className="text-4xl font-bold tracking-tight text-gray-900 sm:text-6xl">
15              Welcome to My Authentication Demo
16            </h1>
17            <p className="mt-6 text-lg leading-8 text-gray-600">
18              A simple example of Next.js authentication with NextAuth.js and MongoDB
19            </p>
20            <div className="mt-10 flex items-center justify-center gap-x-6">
21              {session ? (
22                // Show these buttons if logged in
23                <>
24                  <Link
25                    href="/dashboard"
26                    className="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white"
27                  >
28                    Go to Dashboard
29                  </Link>
30                  <Link
31                    href="/profile"
32                    className="text-sm font-semibold leading-6 text-gray-900"
33                  >
34                    View Profile <span aria-hidden="true">→</span>
35                  </Link>
36                </>
37              ) : (
38                // Show these buttons if logged out
39                <>
40                  <Link
41                    href="/auth/signin"
42                    className="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white"
43                  >
44                    Sign In
45                  </Link>
46                  <Link
47                    href="/auth/register"
48                    className="text-sm font-semibold leading-6 text-gray-900"
49                  >
50                    Create Account <span aria-hidden="true">→</span>
51                  </Link>
52                </>
53              )}
54            </div>
55          </div>
56        </div>
57      </div>
58    </div>
59  );
60}

Step 18: Creating a Navbar Component

A navigation bar helps users move around your app and shows their login status. Create components/Navbar.jsx:

React JSX
1"use client";
2
3import Link from "next/link";
4import { useSession, signOut } from "next-auth/react";
5import { useState } from "react";
6
7export default function Navbar() {
8  const { data: session, status } = useSession();
9  const [isMenuOpen, setIsMenuOpen] = useState(false);
10  
11  return (
12    <nav className="bg-white shadow">
13      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
14        <div className="flex justify-between h-16">
15          <div className="flex">
16            <Link href="/" className="flex-shrink-0 flex items-center">
17              <span className="text-xl font-bold text-indigo-600">AuthApp</span>
18            </Link>
19            <div className="hidden sm:ml-6 sm:flex sm:space-x-8">
20              <Link href="/" className="px-3 py-2 text-sm font-medium text-gray-900">
21                Home
22              </Link>
23              {session && (
24                <Link href="/dashboard" className="px-3 py-2 text-sm font-medium text-gray-900">
25                  Dashboard
26                </Link>
27              )}
28            </div>
29          </div>
30          
31          <div className="hidden sm:ml-6 sm:flex sm:items-center">
32            {status === "loading" ? (
33              <div className="text-sm text-gray-500">Loading...</div>
34            ) : session ? (
35              <div className="flex items-center space-x-4">
36                {session.user?.image && (
37                  <img
38                    className="h-8 w-8 rounded-full"
39                    src={session.user.image}
40                    alt={session.user.name || "User"}
41                  />
42                )}
43                <span className="text-sm text-gray-900">{session.user?.name || session.user?.email}</span>
44                <button
45                  onClick={() => signOut()}
46                  className="text-sm text-red-600 hover:text-red-800"
47                >
48                  Sign out
49                </button>
50              </div>
51            ) : (
52              <div className="space-x-4">
53                <Link href="/auth/signin" className="text-sm text-gray-900 hover:text-gray-600">
54                  Sign in
55                </Link>
56                <Link
57                  href="/auth/register"
58                  className="ml-2 px-3 py-2 rounded-md text-sm text-white bg-indigo-600 hover:bg-indigo-700"
59                >
60                  Register
61                </Link>
62              </div>
63            )}
64          </div>
65          
66          {/* Mobile menu button */}
67          <div className="flex items-center sm:hidden">
68            <button
69              onClick={() => setIsMenuOpen(!isMenuOpen)}
70              className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100"
71            >
72              <span className="sr-only">Open main menu</span>
73              {isMenuOpen ? "✕" : "☰"}
74            </button>
75          </div>
76        </div>
77      </div>
78      
79      {/* Mobile menu */}
80      {isMenuOpen && (
81        <div className="sm:hidden">
82          <div className="pt-2 pb-3 space-y-1">
83            <Link href="/" className="block px-3 py-2 text-base font-medium text-gray-900">
84              Home
85            </Link>
86            {session && (
87              <Link href="/dashboard" className="block px-3 py-2 text-base font-medium text-gray-900">
88                Dashboard
89              </Link>
90            )}
91          </div>
92          <div className="pt-4 pb-3 border-t border-gray-200">
93            {session ? (
94              <div className="flex items-center px-4">
95                {session.user?.image && (
96                  <img
97                    className="h-10 w-10 rounded-full"
98                    src={session.user.image}
99                    alt=""
100                  />
101                )}
102                <div className="ml-3">
103                  <div className="text-base font-medium text-gray-800">
104                    {session.user?.name || session.user?.email}
105                  </div>
106                  <button
107                    onClick={() => signOut()}
108                    className="mt-1 text-sm text-red-600 hover:text-red-800"
109                  >
110                    Sign out
111                  </button>
112                </div>
113              </div>
114            ) : (
115              <div className="flex flex-col space-y-2 px-4">
116                <Link href="/auth/signin" className="text-base font-medium text-gray-900">
117                  Sign in
118                </Link>
119                <Link href="/auth/register" className="text-base font-medium text-gray-900">
120                  Register
121                </Link>
122              </div>
123            )}
124          </div>
125        </div>
126      )}
127    </nav>
128  );
129}

Add this navbar to your layout:

React JSX
1import { Providers } from "./providers";
2import Navbar from "@/components/Navbar";
3import "./globals.css";
4
5export const metadata = {
6  title: "My Auth App",
7  description: "A Next.js app with authentication",
8};
9
10export default function RootLayout({ children }) {
11  return (
12    <html lang="en">
13      <body>
14        <Providers>
15          <Navbar />
16          <main>{children}</main>
17        </Providers>
18      </body>
19    </html>
20  );
21}

Design Tip: This navbar is responsive, working well on both mobile and desktop screens. It automatically shows/hides buttons based on login status.

Step 19: Common Authentication Problems and Solutions

Even when following a guide step-by-step, you might encounter some challenges. Here are solutions to the most common issues:

"My Social Login Doesn't Work"

Problem: Clicking Google or GitHub login buttons doesn't do anything or shows errors.

Solutions:

  • Double-check your environment variables (GOOGLECLIENTID, etc.) for typos
  • Verify that the callback URLs in your provider settings match exactly what NextAuth expects
  • Make sure you've set the NEXTAUTH_URL correctly
  • Check browser console (F12) for error messages

Tip: For Google, make sure you've completed the OAuth consent screen setup and added your email as a test user if you're in testing mode.

"Users Stay Logged In Forever" or "Users Get Logged Out Too Quickly"

Problem: Session duration is too long or too short.

Solution: Adjust the maxAge property in your NextAuth config:

JavaScript
1session: {
2  strategy: "jwt",
3  maxAge: 24 * 60 * 60, // 1 day in seconds (adjust as needed)
4},

"I Can't Get User Data in My Components"

Problem: You're trying to access session data but it's undefined.

Solutions:

  • For client components: Make sure you're using the useSession hook and checking the loading state
  • For server components: Ensure you're using getServerSession with the correct authOptions
  • Verify that the Providers wrapper is correctly set up in your layout

"MongoDB Connection Errors"

Problem: You see errors about MongoDB connection failures.

Solutions:

  • Check your MONGODB_URI for typos
  • Make sure you've whitelisted your IP address in MongoDB Atlas
  • Verify that your database user has the correct permissions
  • For local development, ensure MongoDB is running if you're using a local instance

Debugging Tip: Add console.log statements in your API routes to see what's happening. You can view these logs in your terminal where Next.js is running.

Step 20: Security Best Practices Explained

As you build authentication systems, security becomes crucial. Here are some best practices explained in simple terms:

1. Password Security

  • Always hash passwords
    • never store them as plain text
  • Use a strong hashing algorithm like bcrypt (which we've implemented)
  • Set a reasonable "salt rounds" value (10-12 is good for most applications)

2. Environment Variables

  • Keep all secrets in .env.local files
  • Never commit these files to Git repositories
  • Use different secrets in development and production

3. HTTPS

  • Always use HTTPS in production
  • NextAuth won't work properly without it in production environments
  • Services like Vercel provide HTTPS automatically

4. Rate Limiting

  • Limit how many login attempts are allowed
  • This prevents brute force attacks where attackers try many passwords

A simple rate limiting implementation looks like this:

JavaScript
1// This would go in your NextAuth API route
2import rateLimit from 'express-rate-limit';
3
4// Create a limiter: max 5 requests per minute
5const limiter = rateLimit({
6  windowMs: 60 * 1000, // 1 minute
7  max: 5, // 5 requests per window
8  standardHeaders: true,
9  legacyHeaders: false,
10});
11
12// Apply to your API routes

5. Security Headers

Next.js allows you to add security headers to your app. Create or update next.config.js:

JavaScript
1const nextConfig = {
2  headers: async () => {
3    return [
4      {
5        source: '/(.*)',
6        headers: [
7          {
8            key: 'X-Content-Type-Options',
9            value: 'nosniff',
10          },
11          {
12            key: 'X-Frame-Options',
13            value: 'DENY',
14          },
15          {
16            key: 'X-XSS-Protection',
17            value: '1; mode=block',
18          },
19        ],
20      },
21    ];
22  },
23};
24
25module.exports = nextConfig;

Security Tip: Regularly update your dependencies to get security patches. The npm audit command can help identify vulnerabilities.

Step 21: Deploying Your Secure Application

Understanding Deployment Requirements

Before deploying your authentication system to the internet, there are special considerations:

  1. Environment Variables: Production needs its own set of secure variables
  2. MongoDB Access: Your database needs to be accessible from your hosting provider
  3. OAuth Callback URLs: Must be updated to use your production domain
  4. HTTPS: Required for secure authentication

Getting Ready for Deployment

  1. Create a free account on Vercel (it's built by the same team that makes Next.js!)
  2. Push your code to a GitHub repository
  3. Make sure your MongoDB Atlas cluster allows connections from anywhere (or specifically from Vercel's IP range)
  4. Prepare your environment variables:
    • NEXTAUTH_URL: Set to your production URL (e.g., https://my-auth-app.vercel.app)
    • NEXTAUTH_SECRET: Generate a new secure random string for production
    • OAuth credentials: You may need separate ones for production

Simple Deployment Steps

  1. Connect your GitHub repository to Vercel
  2. Add all your environment variables in the Vercel dashboard
  3. Click "Deploy"
  4. Once deployed, update your OAuth callback URLs in Google and GitHub to include your new domain:
    • https://your-domain.vercel.app/api/auth/callback/google
    • https://your-domain.vercel.app/api/auth/callback/github
  5. Test the authentication flow on your live site

Pro Tip: Use Vercel's "Preview Deployments" feature to test changes before they go live on your main domain.

Step 22: Next Steps and Advanced Features

Once you have your basic authentication system working, you might want to explore more advanced features:

Email Verification

Send verification emails to confirm user addresses:

  1. Add a emailVerified field to your User model
  2. Install a package for sending emails: npm install nodemailer
  3. Create an API route to send verification emails
  4. Add a callback to check verification status:
JavaScript
1// In your NextAuth configuration
2callbacks: {
3  async signIn({ user, account }) {
4    // Allow OAuth sign-ins
5    if (account?.provider !== "credentials") {
6      return true;
7    }
8    
9    // Check if email is verified for credentials
10    await dbConnect();
11    const userDoc = await User.findOne(
12      { email: user.email },
13      { emailVerified: 1 }
14    );
15    
16    return userDoc?.emailVerified ? true : "/auth/verify-email";
17  },
18},

Password Reset

Implement a "forgot password" flow:

  1. Create a form where users can request password resets
  2. Generate a unique, time-limited token
  3. Send an email with a reset link
  4. Create a page where users can enter a new password using this token

Two-Factor Authentication (2FA)

Add an extra layer of security:

  1. Install 2FA libraries: npm install otplib qrcode
  2. Generate and store a secret for each user
  3. Show a QR code for users to scan with authenticator apps
  4. Add a step in the login process to verify the 2FA code

Role-Based Access Control

Restrict different parts of your app to different user types:

  1. Add a role field to your User model (e.g., "user", "admin")
  2. Add the role to the session in the JWT callback
  3. Check the role before allowing access to protected routes
JavaScript
1// Add to your NextAuth configuration
2callbacks: {
3  async jwt({ token, user }) {
4    if (user) {
5      token.role = user.role;
6    }
7    return token;
8  },
9  async session({ session, token }) {
10    if (token && session.user) {
11      session.user.role = token.role;
12    }
13    return session;
14  },
15},

Then check the role in your components or middleware:

JavaScript
1if (session?.user?.role !== "admin") {
2  return <p>Access denied. Admins only.</p>;
3}

Conclusion

Congratulations! You've learned how to implement a secure, production-ready authentication system in Next.js using NextAuth.js and MongoDB. Let's recap what you've accomplished:

  1. Created a Next.js application with authentication built in
  2. Set up OAuth providers (Google and GitHub) for social login
  3. Implemented email/password authentication with secure password hashing
  4. Connected to MongoDB for data storage
  5. Protected routes against unauthorized access
  6. Built user interface components for login, registration, and profile display

This foundation gives you a secure starting point for any web application that requires user accounts and authentication.

What to Learn Next

To continue building your skills, consider exploring:

  • Advanced NextAuth.js features like callbacks and events
  • More complex database schemas and relationships
  • User profile customization and avatar uploads
  • Role-based permissions and access control
  • Enhanced security with Two-Factor Authentication

Helpful Resources

Happy coding, and enjoy building secure applications with Next.js!

Beginner's Guide to Next.js Authentication with NextAuth.js and MongoDB (2025 Edition)

Introduction: What is Authentication and Why Do You Need It?

Ever visited a website that remembers who you are and shows your personal information? That's authentication in action! Authentication is like the digital version of checking someone's ID - it verifies that users are who they claim to be.

Adding authentication to your Next.js website is important because it:

  • Keeps user information safe from unauthorized access
  • Allows you to create personalized experiences for each user
  • Protects private sections of your website
  • Builds trust with your visitors who know their data is secure
  • Helps comply with data protection regulations

NextAuth.js makes adding authentication to your Next.js app super easy, even if you're a beginner. It works with popular login methods like Google and GitHub, and you can also use traditional email/password login. Best of all, we'll connect it to MongoDB, a beginner-friendly database that's perfect for web applications!

What You'll Build in This Tutorial

By the end of this beginner-friendly guide, you'll create a Next.js application with:

  1. A login page with social login buttons (Google and GitHub)
  2. Secure email/password login that properly protects passwords
  3. Protected pages that only logged-in users can access
  4. User profiles showing information from their social accounts
  5. Data storage in MongoDB, a popular and free database
  6. Session management that keeps users logged in

![Authentication Flow Example]

What You'll Need Before Starting

  • Basic knowledge of JavaScript and React (you don't need to be an expert!)
  • Node.js installed on your computer (download from nodejs.org)
  • A code editor like Visual Studio Code (it's free!)
  • A free MongoDB Atlas account (we'll show you how to set this up)
  • A free GitHub or Google account for setting up social login

Don't worry if you're new to some of these technologies - we'll explain each step clearly!

Step 1: Creating Your Next.js Project

Let's start with a fresh Next.js application:

Bash
1npx create-next-app@latest my-secure-app
2cd my-secure-app

This command creates a new Next.js project with the latest features, including the App Router for enhanced routing capabilities.

Step 2: Installing NextAuth.js

Add NextAuth.js to your project dependencies:

Bash
npm install next-auth@latest @types/next-auth

The latest version includes improved TypeScript support and enhanced security features for your authentication system.

Step 3: Setting Up Environment Variables

When building apps with login features, you need to keep certain information secret, like API keys and passwords. Let's set those up:

  1. Create a file named .env.local in your project's main folder
  2. Add the following lines to it:
Plain Text
1NEXTAUTH_URL=http://localhost:3000
2NEXTAUTH_SECRET=your_secure_key_here
3MONGODB_URI=your_mongodb_connection_string
4GOOGLE_CLIENT_ID=your_google_id
5GOOGLE_CLIENT_SECRET=your_google_secret
6GITHUB_CLIENT_ID=your_github_id
7GITHUB_CLIENT_SECRET=your_github_secret

For the NEXTAUTH_SECRET, you need a random, secure string. You can generate one by:

On Mac/Linux: Open Terminal and type:

Bash
openssl rand -base64 32

On Windows: Open PowerShell and type:

Powershell
[Convert]::ToBase64String([System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32))

Copy the result and paste it as your NEXTAUTH_SECRET.

Important for beginners: Never share your .env.local file or upload it to GitHub! This file contains secret information that should stay private. Next.js automatically prevents this file from being sent to the browser, keeping your secrets safe.

Step 4: Configuring OAuth Providers

Setting Up Google Authentication (with Screenshots)

Let's set up Google login for your app:

  1. Go to the Google Cloud Console
  2. Create a new project by clicking the project dropdown at the top of the page and selecting "New Project" Google Cloud New Project
  3. Give your project a name like "My Auth App" and click "Create"
  4. Once your project is created, select it and go to the sidebar menu
  5. Click on "APIs & Services" > "Credentials"
  6. On the Credentials page, click the "Create Credentials" button and select "OAuth client ID" Create OAuth Client ID
  7. You might need to configure the consent screen first - click "Configure Consent Screen"
    • Choose "External" (available to any Google user)
    • Fill in the required app information (name, user support email, developer contact)
    • You can leave most fields as default for testing
    • Add your email as a test user
    • Click "Save and Continue" through each section
  8. Back on the credentials page, click "Create Credentials" > "OAuth Client ID" again
  9. Select "Web Application" as the application type
  10. Give it a name like "Next.js Web Client"
  11. Under "Authorized JavaScript origins" add http://localhost:3000
  12. Under "Authorized redirect URIs" add http://localhost:3000/api/auth/callback/google Authorized Redirect URIs
  13. Click "Create"
  14. A popup will show your Client ID and Client Secret - copy these
  15. Paste them into your .env.local file as GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET

That's it! Your Google authentication is now set up for local development.

Setting Up GitHub Authentication (Easy Step-by-Step)

Now let's add GitHub login to your app:

  1. Sign in to your GitHub account
  2. Go to GitHub Developer Settings (Click your profile picture > Settings > Developer settings > OAuth Apps)
  3. Click the "New OAuth App" button
  4. Fill in the form:
    • Application name: "My Next.js Auth App" (or any name you like)
    • Homepage URL: http://localhost:3000
    • Application description: "A Next.js app with authentication" (optional)
    • Authorization callback URL: http://localhost:3000/api/auth/callback/github
  5. Click "Register application"
  6. On the next screen, you'll see your Client ID
  7. Click "Generate a new client secret"
  8. Copy both the Client ID and the new Client Secret
  9. Add them to your .env.local file as GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET

Now your app can use both Google and GitHub for login!

Step 5: Creating the NextAuth API Route

Create app/api/auth/[...nextauth]/route.ts:

TypeScript
1import NextAuth from "next-auth";
2import GoogleProvider from "next-auth/providers/google";
3import GitHubProvider from "next-auth/providers/github";
4import CredentialsProvider from "next-auth/providers/credentials";
5import { compare } from "bcryptjs";
6import { MongoDBAdapter } from "@auth/mongodb-adapter";
7import clientPromise from "@/lib/mongodb-client";
8import User from "@/models/User";
9import dbConnect from "@/lib/mongodb";
10
11// Create a simplified MongoDB client for the adapter
12export const authOptions = {
13  adapter: MongoDBAdapter(clientPromise),
14  providers: [
15    GoogleProvider({
16      clientId: process.env.GOOGLE_CLIENT_ID!,
17      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
18    }),
19    GitHubProvider({
20      clientId: process.env.GITHUB_CLIENT_ID!,
21      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
22    }),
23    CredentialsProvider({
24      name: "Email",
25      credentials: {
26        email: { label: "Email", type: "email" },
27        password: { label: "Password", type: "password" }
28      },
29      async authorize(credentials) {
30        if (!credentials?.email || !credentials?.password) {
31          return null;
32        }
33        
34        // Connect to database
35        await dbConnect();
36        
37        // Find user by email
38        const user = await User.findOne({ email: credentials.email });
39        
40        if (!user || !user.password) {
41          return null;
42        }
43        
44        // Compare provided password with stored hash
45        const isPasswordValid = await compare(
46          credentials.password,
47          user.password
48        );
49        
50        if (!isPasswordValid) {
51          return null;
52        }
53        
54        // Return user object if credentials are valid
55        return {
56          id: user._id.toString(),
57          email: user.email,
58          name: user.name,
59          image: user.image,
60        };
61      }
62    }),
63  ],
64  session: {
65    strategy: "jwt",
66    maxAge: 8 * 60 * 60, // 8 hours
67  },
68  callbacks: {
69    async session({ session, token }) {
70      if (token && session.user) {
71        session.user.id = token.sub;
72        // Add additional user data to the session if needed
73        // For example, if you want to add a role field:
74        // session.user.role = token.role;
75      }
76      return session;
77    },
78  },
79  pages: {
80    signIn: "/auth/signin",
81    error: "/auth/error",
82  },
83};
84
85const handler = NextAuth(authOptions);
86export { handler as GET, handler as POST };

This configuration includes:

  • Multiple authentication providers
  • Session management with JWT
  • Custom callback functions for session handling
  • Custom pages for the authentication flow

Step 6: Setting Up MongoDB with Mongoose

For persistent user data and credentials management, let's connect NextAuth.js to MongoDB using Mongoose:

  1. Install MongoDB and Mongoose packages:

    Bash
    npm install mongodb mongoose @auth/mongodb-adapter
  2. Set up your MongoDB connection string in .env.local:

    Plain Text
    MONGODB_URI=mongodb+srv://yourusername:yourpassword@yourcluster.mongodb.net/nextauth?retryWrites=true&w=majority

Note for beginners: Replace the connection string with your actual MongoDB connection string. You can get this from MongoDB Atlas (free tier available) or your local MongoDB installation.

  1. Create a database connection file in lib/mongodb.js: ```javascript import mongoose from 'mongoose';

const MONGODBURI = process.env.MONGODBURI;

if (!MONGODBURI) { throw new Error('Please define the MONGODBURI environment variable'); }

/**

  • Global is used here to maintain a cached connection across hot reloads
  • in development. This prevents connections growing exponentially
  • during API Route usage. */ let cached = global.mongoose;

if (!cached) { cached = global.mongoose = { conn: null, promise: null }; }

async function dbConnect() { if (cached.conn) {

return cached.conn;

}

if (!cached.promise) {

Plain Text
1const opts = {
2  bufferCommands: false,
3};
4
5cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
6  return mongoose;
7});

}

cached.conn = await cached.promise; return cached.conn; }

export default dbConnect;

Plain Text
4. Create user models in `models/User.js`:

javascript import mongoose from 'mongoose';

// Check if the User model already exists to prevent overwriting const UserSchema = new mongoose.Schema({ name: String, email: {

Plain Text
1type: String,
2unique: true,
3sparse: true,

}, emailVerified: Date, image: String, password: String, });

export default mongoose.models.User || mongoose.model('User', UserSchema);

Plain Text
5. Create your Account model in `models/Account.js`:

javascript import mongoose from 'mongoose';

const AccountSchema = new mongoose.Schema({ userId: {

Plain Text
1type: mongoose.Schema.Types.ObjectId,
2ref: 'User',

}, type: String, provider: String, providerAccountId: String, refreshtoken: String, accesstoken: String, expiresat: Number, tokentype: String, scope: String, idtoken: String, sessionstate: String, });

// Compound index to ensure unique provider + providerAccountId AccountSchema.index({ provider: 1, providerAccountId: 1 }, { unique: true });

export default mongoose.models.Account || mongoose.model('Account', AccountSchema);

Plain Text
6. Create your Session model in `models/Session.js`:

javascript import mongoose from 'mongoose';

const SessionSchema = new mongoose.Schema({ userId: {

Plain Text
1type: mongoose.Schema.Types.ObjectId,
2ref: 'User',

}, expires: Date, sessionToken: {

Plain Text
1type: String,
2unique: true,

}, });

export default mongoose.models.Session || mongoose.model('Session', SessionSchema);

Plain Text
7. Create a VerificationToken model in `models/VerificationToken.js`:

javascript import mongoose from 'mongoose';

const VerificationTokenSchema = new mongoose.Schema({ identifier: String, token: {

Plain Text
1type: String,
2unique: true,

}, expires: Date, });

// Compound index for identifier + token VerificationTokenSchema.index({ identifier: 1, token: 1 }, { unique: true });

export default mongoose.models.VerificationToken || mongoose.model('VerificationToken', VerificationTokenSchema);

Plain Text
These models form the foundation of your authentication system, storing users, their linked accounts, active sessions, and verification tokens for email verification.

Step 7: Building the SignIn Page

Create app/auth/signin/page.tsx:

React TSX
1"use client";
2
3import { useState } from "react";
4import { signIn, getProviders } from "next-auth/react";
5import { useRouter } from "next/navigation";
6import Image from "next/image";
7
8export default function SignIn() {
9  const [email, setEmail] = useState("");
10  const [password, setPassword] = useState("");
11  const [error, setError] = useState("");
12  const [loading, setLoading] = useState(false);
13  const [providers, setProviders] = useState<any>(null);
14  const router = useRouter();
15
16  useState(() => {
17    const loadProviders = async () => {
18      const providers = await getProviders();
19      setProviders(providers);
20    };
21    loadProviders();
22  }, []);
23
24  const handleEmailSignIn = async (e: React.FormEvent) => {
25    e.preventDefault();
26    setLoading(true);
27    setError("");
28
29    try {
30      const result = await signIn("credentials", {
31        redirect: false,
32        email,
33        password,
34      });
35
36      if (result?.error) {
37        setError("Invalid email or password");
38      } else {
39        router.push("/dashboard");
40      }
41    } catch (error) {
42      setError("An unexpected error occurred");
43    } finally {
44      setLoading(false);
45    }
46  };
47
48  return (
49    <div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4">
50      <div className="max-w-md w-full space-y-8 bg-white p-8 rounded-xl shadow-lg">
51        <div className="text-center">
52          <h1 className="text-3xl font-bold text-gray-900">Sign In</h1>
53          <p className="mt-2 text-gray-600">Access your secure dashboard</p>
54        </div>
55        
56        {error && (
57          <div className="bg-red-50 text-red-500 p-3 rounded-lg text-sm">
58            {error}
59          </div>
60        )}
61
62        <form className="mt-8 space-y-6" onSubmit={handleEmailSignIn}>
63          <div>
64            <label htmlFor="email" className="block text-sm font-medium text-gray-700">
65              Email address
66            </label>
67            <input
68              id="email"
69              name="email"
70              type="email"
71              autoComplete="email"
72              required
73              value={email}
74              onChange={(e) => setEmail(e.target.value)}
75              className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
76            />
77          </div>
78          
79          <div>
80            <label htmlFor="password" className="block text-sm font-medium text-gray-700">
81              Password
82            </label>
83            <input
84              id="password"
85              name="password"
86              type="password"
87              autoComplete="current-password"
88              required
89              value={password}
90              onChange={(e) => setPassword(e.target.value)}
91              className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
92            />
93          </div>
94
95          <div>
96            <button
97              type="submit"
98              disabled={loading}
99              className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
100            >
101              {loading ? "Signing in..." : "Sign in with Email"}
102            </button>
103          </div>
104        </form>
105
106        <div className="mt-6">
107          <div className="relative">
108            <div className="absolute inset-0 flex items-center">
109              <div className="w-full border-t border-gray-300" />
110            </div>
111            <div className="relative flex justify-center text-sm">
112              <span className="px-2 bg-white text-gray-500">Or continue with</span>
113            </div>
114          </div>
115
116          <div className="mt-6 grid grid-cols-2 gap-3">
117            {providers && 
118              Object.values(providers)
119                .filter((provider: any) => provider.id !== "credentials")
120                .map((provider: any) => (
121                <button
122                  key={provider.id}
123                  onClick={() => signIn(provider.id, { callbackUrl: "/dashboard" })}
124                  className="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
125                >
126                  {provider.id === "google" && (
127                    <>
128                      <svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
129                        <path
130                          fill="currentColor"
131                          d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
132                        />
133                      </svg>
134                      Google
135                    </>
136                  )}
137                  {provider.id === "github" && (
138                    <>
139                      <svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
140                        <path
141                          fill="currentColor"
142                          d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
143                        />
144                      </svg>
145                      GitHub
146                    </>
147                  )}
148                </button>
149              ))}
150          </div>
151        </div>
152      </div>
153    </div>
154  );
155}

This enhanced sign-in page includes:

  • Email/password authentication
  • Social login options
  • Form validation
  • Error handling
  • Loading states
  • Responsive design with Tailwind CSS

Step 8: Creating a Provider Wrapper

To ensure NextAuth client components work properly, create a provider wrapper in app/providers.tsx:

React TSX
1"use client";
2
3import { SessionProvider } from "next-auth/react";
4
5export function Providers({ children }: { children: React.ReactNode }) {
6  return <SessionProvider>{children}</SessionProvider>;
7}

Then update your app/layout.tsx:

React TSX
1import { Providers } from "./providers";
2import "./globals.css";
3
4export const metadata = {
5  title: "Secure Next.js App",
6  description: "A secure Next.js application with NextAuth.js",
7};
8
9export default function RootLayout({
10  children,
11}: {
12  children: React.ReactNode;
13}) {
14  return (
15    <html lang="en">
16      <body>
17        <Providers>{children}</Providers>
18      </body>
19    </html>
20  );
21}

Step 9: Protecting Routes

Server-Side Protection

Create app/dashboard/page.tsx:

React TSX
1import { getServerSession } from "next-auth";
2import { authOptions } from "@/app/api/auth/[...nextauth]/route";
3import { redirect } from "next/navigation";
4import UserProfile from "@/components/UserProfile";
5
6export default async function Dashboard() {
7  const session = await getServerSession(authOptions);
8
9  if (!session) {
10    redirect("/auth/signin");
11  }
12
13  return (
14    <div className="max-w-4xl mx-auto p-6">
15      <h1 className="text-3xl font-bold mb-6">Dashboard</h1>
16      <div className="bg-white shadow rounded-lg p-6">
17        <UserProfile />
18        
19        <div className="mt-8">
20          <h2 className="text-xl font-semibold mb-4">Your Secure Content</h2>
21          <p>This content is only visible to authenticated users.</p>
22        </div>
23      </div>
24    </div>
25  );
26}

Client-Side Protection with useSession Hook

Create components/UserProfile.tsx:

React TSX
1"use client";
2
3import { useSession, signOut } from "next-auth/react";
4import Image from "next/image";
5
6export default function UserProfile() {
7  const { data: session, status } = useSession();
8  
9  if (status === "loading") {
10    return <div className="animate-pulse">Loading user profile...</div>;
11  }
12
13  if (!session) {
14    return <p>Please sign in to view your profile</p>;
15  }
16
17  return (
18    <div className="flex items-center space-x-4">
19      {session.user?.image ? (
20        <div className="relative w-16 h-16 rounded-full overflow-hidden">
21          <Image 
22            src={session.user.image} 
23            alt="Profile picture"
24            fill
25            className="object-cover"
26          />
27        </div>
28      ) : (
29        <div className="w-16 h-16 bg-gray-200 rounded-full flex items-center justify-center">
30          <span className="text-2xl text-gray-600">
31            {session.user?.name?.charAt(0) || session.user?.email?.charAt(0) || '?'}
32          </span>
33        </div>
34      )}
35      
36      <div>
37        {session.user?.name && (
38          <h2 className="text-xl font-semibold">{session.user.name}</h2>
39        )}
40        {session.user?.email && (
41          <p className="text-gray-600">{session.user.email}</p>
42        )}
43        <button
44          onClick={() => signOut({ callbackUrl: "/" })}
45          className="mt-2 text-sm text-red-600 hover:text-red-800"
46        >
47          Sign out
48        </button>
49      </div>
50    </div>
51  );
52}

Step 10: Creating a Middleware for Multiple Protected Routes

For protecting multiple routes at once, create middleware.ts in your project root:

TypeScript
1import { NextResponse } from "next/server";
2import { NextRequest } from "next/server";
3import { getToken } from "next-auth/jwt";
4
5export async function middleware(request: NextRequest) {
6  const { pathname } = request.nextUrl;
7  
8  // Protect these paths
9  const protectedPaths = ["/dashboard", "/profile", "/settings"];
10  const isPathProtected = protectedPaths.some((path) => 
11    pathname.startsWith(path)
12  );
13  
14  if (isPathProtected) {
15    const token = await getToken({
16      req: request,
17      secret: process.env.NEXTAUTH_SECRET,
18    });
19    
20    // Redirect to login if user is not authenticated
21    if (!token) {
22      const url = new URL(`/auth/signin`, request.url);
23      url.searchParams.set("callbackUrl", pathname);
24      return NextResponse.redirect(url);
25    }
26  }
27  
28  return NextResponse.next();
29}
30
31export const config = {
32  matcher: ["/dashboard/:path*", "/profile/:path*", "/settings/:path*"],
33};

Advanced Security Practices

Implementing Password Hashing

Install bcryptjs:

Bash
npm install bcryptjs @types/bcryptjs

Create a utility function in utils/auth.ts:

TypeScript
1import { hash, compare } from "bcryptjs";
2
3export async function hashPassword(password: string): Promise<string> {
4  return await hash(password, 12);
5}
6
7export async function verifyPassword(
8  plainPassword: string, 
9  hashedPassword: string
10): Promise<boolean> {
11  return await compare(plainPassword, hashedPassword);
12}

Implementing Rate Limiting

Install rate limiting packages:

Bash
npm install @upstash/redis @upstash/ratelimit

Create a rate limiter in your auth API:

TypeScript
1import { Ratelimit } from "@upstash/ratelimit";
2import { Redis } from "@upstash/redis";
3
4// Create a new ratelimiter that allows 5 requests per minute
5const ratelimit = new Ratelimit({
6  redis: Redis.fromEnv(),
7  limiter: Ratelimit.slidingWindow(5, "1 m"),
8  analytics: true,
9});
10
11// In your auth route handler
12export async function POST(request: Request) {
13  const ip = request.headers.get("x-forwarded-for") || "unknown";
14  const { success } = await ratelimit.limit(ip);
15  
16  if (!success) {
17    return new Response("Too many requests", { status: 429 });
18  }
19  
20  // Continue with normal auth handling
21}

Testing Your Authentication System

Testing with Jest and React Testing Library

Install testing dependencies:

Bash
npm install --save-dev jest @testing-library/react @testing-library/jest-dom jest-environment-jsdom

Create a test for your SignIn component:

TypeScript
1// __tests__/SignIn.test.tsx
2import { render, screen, fireEvent, waitFor } from '@testing-library/react';
3import SignIn from '@/app/auth/signin/page';
4import { signIn } from 'next-auth/react';
5
6// Mock NextAuth
7jest.mock('next-auth/react', () => ({
8  signIn: jest.fn(),
9  getProviders: jest.fn().mockResolvedValue({
10    google: { id: 'google', name: 'Google' },
11  }),
12}));
13
14// Mock Next Router
15jest.mock('next/navigation', () => ({
16  useRouter: jest.fn().mockReturnValue({
17    push: jest.fn(),
18  }),
19}));
20
21describe('SignIn Page', () => {
22  it('renders sign in form', async () => {
23    render(<SignIn />);
24    
25    expect(screen.getByLabelText(/email address/i)).toBeInTheDocument();
26    expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
27    expect(screen.getByRole('button', { name: /sign in with email/i })).toBeInTheDocument();
28  });
29
30  it('handles form submission', async () => {
31    (signIn as jest.Mock).mockResolvedValueOnce({ error: null });
32    
33    render(<SignIn />);
34    
35    fireEvent.change(screen.getByLabelText(/email address/i), {
36      target: { value: 'test@example.com' },
37    });
38    
39    fireEvent.change(screen.getByLabelText(/password/i), {
40      target: { value: 'password123' },
41    });
42    
43    fireEvent.click(screen.getByRole('button', { name: /sign in with email/i }));
44    
45    await waitFor(() => {
46      expect(signIn).toHaveBeenCalledWith('credentials', {
47        redirect: false,
48        email: 'test@example.com',
49        password: 'password123',
50      });
51    });
52  });
53});

Deploying Your Secure Application

Deployment Checklist

Before deploying to production:

  1. Update your environment variables on the production server
  2. Set NEXTAUTH_URL to your production URL
  3. Generate a new strong NEXTAUTH_SECRET for production
  4. Configure proper CORS settings
  5. Set up a database for persistance
  6. Ensure all callback URLs in your OAuth providers are updated to your production domain
  7. Implement HTTPS

Vercel Deployment

To deploy to Vercel:

  1. Push your code to GitHub
  2. Connect your repository to Vercel
  3. Configure environment variables in the Vercel dashboard
  4. Deploy your application

Common Problems and How to Fix Them

Even when following a guide step-by-step, you might run into some issues. Here are simple solutions to the most common problems:

"My Login Isn't Working"

If you click login buttons and nothing happens or you get errors:

  1. Check Your Environment Variables: Make sure your .env.local file has all the correct values and there are no typos
  2. Verify Redirect URLs: Double-check that the callback URLs in Google and GitHub settings exactly match your app's URLs
  3. Look at Browser Console: Press F12 in your browser and check the Console tab for error messages
  4. Check NextAuth Secret: Make sure your NEXTAUTH_SECRET is set and doesn't contain any special characters

"I Keep Getting Logged Out"

If your login doesn't stay active between page refreshes:

  1. Session Provider: Make sure you've added the SessionProvider to your `layout.

Next Steps and Additional Features

Implementing Email Verification

Add email verification using a verification token:

TypeScript
1// In your NextAuth configuration
2callbacks: {
3  async signIn({ user, account }) {
4    // Allow OAuth sign-ins
5    if (account?.provider !== "credentials") {
6      return true;
7    }
8    
9    // Connect to the database
10    await dbConnect();
11    
12    // Check if email is verified for credentials
13    const userDoc = await User.findOne(
14      { email: user.email },
15      { emailVerified: 1 }
16    );
17    
18    return userDoc?.emailVerified ? true : "/auth/verify-email";
19  },
20},

Adding Two-Factor Authentication

For enhanced security, implement 2FA using:

Bash
npm install otplib qrcode

Conclusion

Congratulations! You've successfully implemented a robust authentication system in your Next.js application using NextAuth.js. This foundation gives you:

  • Secure multi-provider authentication
  • Protected routes on both client and server
  • A customizable user experience
  • Industry-standard security practices

Remember that security is an ongoing process. Keep your dependencies updated, follow security best practices, and consider implementing additional security measures as your application grows.

Additional Resources

Last updated: 6/8/2025