Implementing Advanced Authentication in Next.js 16 with NextAuth.js
In the modern web development landscape, securing user authentication is crucial. Next.js 16, combined with NextAuth.js, offers a powerful solution for implementing authentication in your applications. NextAuth.js is a complete open-source authentication solution for Next.js applications, providing a flexible and secure way to manage user sessions.
This tutorial will guide you through setting up advanced authentication in a Next.js 16 project using NextAuth.js. We will cover the necessary steps to get a basic authentication system up and running, and explore features like social login and secure session management.
Prerequisites
Before we begin, ensure you have the following installed on your machine:
- Node.js (version 16 or later)
- npm (Node package manager)
You can verify your Node.js and npm versions by running:
node -v
npm -v
If you need to install Node.js and npm, you can download them from the official Node.js website.
Additionally, ensure you have a basic understanding of JavaScript and familiarity with Next.js.
Project Structure
Let's start by setting up the project structure. We'll create a new Next.js application and install NextAuth.js.
npx create-next-app@latest nextauth-demo
cd nextauth-demo
npm install next-auth
Your project directory should look like this:
nextauth-demo/
├── node_modules/
├── public/
├── styles/
├── pages/
│ ├── api/
│ │ └── auth/
│ ├── _app.js
│ └── index.js
├── .gitignore
├── package.json
└── README.md
Step 1: Set Up NextAuth.js
First, we'll configure NextAuth.js to handle authentication. Create a new file [...]nextauth.js inside the pages/api/auth directory:
touch pages/api/auth/[...nextauth].js
Add the following code to configure NextAuth.js:
// pages/api/auth/[...nextauth].js
import NextAuth from "next-auth";
import Providers from "next-auth/providers";
export default NextAuth({
providers: [
Providers.Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
secret: process.env.NEXTAUTH_SECRET,
});
Explanation:
- We're using Google as an authentication provider. You'll need to set up a Google OAuth client to get the
clientIdandclientSecret. - The
secretis used to encrypt the session, which you should set in your environment variables.
Step 2: Configure Environment Variables
NextAuth.js requires certain environment variables to be set for security and provider configuration. Create a .env.local file in the root of your project:
touch .env.local
Add the following environment variables:
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
NEXTAUTH_SECRET=your-nextauth-secret
Explanation:
- Replace
your-google-client-idandyour-google-client-secretwith the credentials from your Google OAuth setup. NEXTAUTH_SECRETshould be a random string used to encrypt session data.
Step 3: Create a Login Page
Now, let's create a simple login page where users can authenticate via Google. Create a new file login.js inside the pages directory:
touch pages/login.js
Add the following code to the login.js file:
// pages/login.js
import { signIn, signOut, useSession } from "next-auth/react";
export default function LoginPage() {
const { data: session } = useSession();
return (
<div>
{!session ? (
<>
<h1>Login</h1>
<button onClick={() => signIn("google")}>Sign in with Google</button>
</>
) : (
<>
<h1>Welcome, {session.user.name}</h1>
<button onClick={() => signOut()}>Sign out</button>
</>
)}
</div>
);
}
Explanation:
- We use the
useSessionhook from NextAuth.js to get the session data. - If a user is not logged in, we display a button to sign in with Google.
- If a user is logged in, we greet them by name and provide a sign-out button.
With these steps, you have a basic authentication setup using NextAuth.js in your Next.js application. In the next part of this tutorial, we will cover more advanced features and configurations.
```markdown
## Step 4: Protecting Routes
To ensure that only authenticated users can access certain pages, we need to protect our routes. Let's create a higher-order component (HOC) that will wrap around pages requiring authentication.
Create a new file `requireAuth.js` inside a `components` directory:
```bash
mkdir components
touch components/requireAuth.js
Add the following code to requireAuth.js:
// components/requireAuth.js
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect } from "react";
export default function requireAuth(Component) {
return function AuthenticatedComponent(props) {
const { data: session, status } = useSession();
const router = useRouter();
useEffect(() => {
if (status === "loading") return; // Do nothing while loading
if (!session) router.push("/login"); // Redirect to login if not authenticated
}, [session, status]);
if (status === "loading" || !session) {
return <p>Loading...</p>; // Show loading while checking session
}
return <Component {...props} />;
};
}
Explanation:
- This HOC checks if a user is authenticated using the
useSessionhook. - If the session is loading, it displays a loading message.
- If the user is not authenticated, it redirects them to the login page.
- If authenticated, it renders the wrapped component.
Step 5: Apply Route Protection
Let's apply our requireAuth HOC to a protected page. Create a new file dashboard.js inside the pages directory:
touch pages/dashboard.js
Add the following code to dashboard.js:
// pages/dashboard.js
import requireAuth from "../components/requireAuth";
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<p>This is a protected route. Only authenticated users can see this.</p>
</div>
);
}
export default requireAuth(Dashboard);
Explanation:
- We wrap the
Dashboardcomponent withrequireAuthto ensure only authenticated users can access it.
Complete Working Example
Here's a summary of the full files for a working authentication system:
pages/api/auth/[...nextauth].js
import NextAuth from "next-auth";
import Providers from "next-auth/providers";
export default NextAuth({
providers: [
Providers.Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
secret: process.env.NEXTAUTH_SECRET,
});
.env.local
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
NEXTAUTH_SECRET=your-nextauth-secret
pages/login.js
import { signIn, signOut, useSession } from "next-auth/react";
export default function LoginPage() {
const { data: session } = useSession();
return (
<div>
{!session ? (
<>
<h1>Login</h1>
<button onClick={() => signIn("google")}>Sign in with Google</button>
</>
) : (
<>
<h1>Welcome, {session.user.name}</h1>
<button onClick={() => signOut()}>Sign out</button>
</>
)}
</div>
);
}
components/requireAuth.js
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect } from "react";
export default function requireAuth(Component) {
return function AuthenticatedComponent(props) {
const { data: session, status } = useSession();
const router = useRouter();
useEffect(() => {
if (status === "loading") return;
if (!session) router.push("/login");
}, [session, status]);
if (status === "loading" || !session) {
return <p>Loading...</p>;
}
return <Component {...props} />;
};
}
pages/dashboard.js
import requireAuth from "../components/requireAuth";
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<p>This is a protected route. Only authenticated users can see this.</p>
</div>
);
}
export default requireAuth(Dashboard);
Common Errors and Fixes
Error: OAuthCallbackError: Missing client_id
- Cause: The
GOOGLE_CLIENT_IDenvironment variable is not set correctly. - Fix: Ensure your
.env.localfile contains the correctGOOGLE_CLIENT_ID.
Error: Cannot find module 'next-auth/providers'
- Cause: NextAuth.js is not installed.
- Fix: Run
npm install next-authto install the package.
Error: Invalid secret
- Cause: The
NEXTAUTH_SECRETis missing or incorrect. - Fix: Set a valid
NEXTAUTH_SECRETin your.env.localfile.
Conclusion
You've now set up a basic authentication system using NextAuth.js in a Next.js 16 application. This tutorial covered setting up Google as an authentication provider, protecting routes, and handling sessions. You can expand this setup by adding more providers and customizing session handling.