Skip to main content

Next.js NextAuth.js

Authentication is a critical aspect of most web applications. In the Next.js ecosystem, NextAuth.js has emerged as the go-to solution for implementing authentication. This comprehensive guide will walk you through implementing authentication in your Next.js projects using NextAuth.js.

What is NextAuth.js?

NextAuth.js is an open-source authentication solution specifically designed for Next.js applications. It provides a simple way to add authentication to your Next.js application with support for:

  • OAuth providers (Google, GitHub, Facebook, etc.)
  • Email/passwordless authentication
  • Credentials-based authentication (username/password)
  • JWT and database sessions
  • Customizable callbacks and events

Getting Started with NextAuth.js

Step 1: Installation

First, let's install NextAuth.js in your Next.js project:

bash
npm install next-auth
# or
yarn add next-auth
# or
pnpm add next-auth

Step 2: Setting up API Routes

Create a file at pages/api/auth/[...nextauth].js (for Pages Router) or app/api/auth/[...nextauth]/route.js (for App Router) to handle authentication.

For Pages Router:

javascript
import NextAuth from 'next-auth';
import GithubProvider from 'next-auth/providers/github';

export default NextAuth({
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
// Add more providers here
],
// Additional configuration options
});

For App Router:

javascript
import NextAuth from 'next-auth';
import GithubProvider from 'next-auth/providers/github';

const handler = NextAuth({
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
// Add more providers here
],
// Additional configuration options
});

export { handler as GET, handler as POST };

Step 3: Environment Variables

Create a .env.local file in your project root and add your provider credentials:

GITHUB_ID=your_github_client_id
GITHUB_SECRET=your_github_client_secret
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your_nextauth_secret

The NEXTAUTH_SECRET is used to encrypt the NextAuth.js JWT. You can generate a random string for development.

Authentication Providers

NextAuth.js supports many authentication providers. Let's look at some common ones:

OAuth Providers

OAuth providers allow users to log in using their accounts from other services.

javascript
import GoogleProvider from 'next-auth/providers/google';
import FacebookProvider from 'next-auth/providers/facebook';

// Inside your NextAuth configuration:
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
FacebookProvider({
clientId: process.env.FACEBOOK_ID,
clientSecret: process.env.FACEBOOK_SECRET,
}),
],

Email Provider

For passwordless authentication via magic links sent to email:

javascript
import EmailProvider from 'next-auth/providers/email';

providers: [
EmailProvider({
server: {
host: process.env.EMAIL_SERVER_HOST,
port: process.env.EMAIL_SERVER_PORT,
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD,
},
},
from: process.env.EMAIL_FROM,
}),
],

Credentials Provider

For traditional username/password authentication:

javascript
import CredentialsProvider from 'next-auth/providers/credentials';

providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
username: { label: "Username", type: "text" },
password: { label: "Password", type: "password" }
},
async authorize(credentials, req) {
// Add your own logic here to validate credentials
// Return user object if valid, null if invalid
const user = await findUserByCredentials(credentials);
if (user) {
return user;
} else {
return null;
}
}
})
],

Using Authentication in Your Components

Session Provider

To use authentication in your components, you need to wrap your application with a session provider. Add this to your _app.js (Pages Router) or layout.js (App Router):

For Pages Router (_app.js):

jsx
import { SessionProvider } from 'next-auth/react';

function MyApp({ Component, pageProps: { session, ...pageProps } }) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
);
}

export default MyApp;

For App Router (layout.js):

jsx
'use client'

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

export default function RootLayout({ children }) {
return (
<html>
<body>
<SessionProvider>{children}</SessionProvider>
</body>
</html>
);
}

Using Session in Components

Client-Side Authentication (Pages Router)

jsx
import { useSession, signIn, signOut } from 'next-auth/react';

export default function Component() {
const { data: session, status } = useSession();

if (status === "loading") {
return <p>Loading...</p>;
}

if (session) {
return (
<>
<p>Signed in as {session.user.email}</p>
<button onClick={() => signOut()}>Sign out</button>
</>
);
}

return (
<>
<p>Not signed in</p>
<button onClick={() => signIn()}>Sign in</button>
</>
);
}

Client-Side Authentication (App Router)

jsx
'use client'

import { useSession, signIn, signOut } from 'next-auth/react';

export default function Component() {
const { data: session, status } = useSession();

if (status === "loading") {
return <p>Loading...</p>;
}

if (session) {
return (
<>
<p>Signed in as {session.user.email}</p>
<button onClick={() => signOut()}>Sign out</button>
</>
);
}

return (
<>
<p>Not signed in</p>
<button onClick={() => signIn()}>Sign in</button>
</>
);
}

Server-Side Authentication (Pages Router)

jsx
import { getServerSession } from 'next-auth/next';
import { authOptions } from './api/auth/[...nextauth]';

export default function ServerComponent({ session }) {
return (
<>
{session ? (
<p>Welcome {session.user.name}!</p>
) : (
<p>Please sign in</p>
)}
</>
);
}

export async function getServerSideProps(context) {
const session = await getServerSession(context.req, context.res, authOptions);

return {
props: {
session,
},
};
}

Server-Side Authentication (App Router)

jsx
import { getServerSession } from 'next-auth/next';
import { authOptions } from '../api/auth/[...nextauth]/route';

export default async function ServerComponent() {
const session = await getServerSession(authOptions);

return (
<>
{session ? (
<p>Welcome {session.user.name}!</p>
) : (
<p>Please sign in</p>
)}
</>
);
}

Protecting Routes

Client-Side Route Protection

You can create a simple HOC (Higher Order Component) to protect routes:

jsx
'use client'

import { useSession } from 'next-auth/react';
import { useRouter } from 'next/router';
import { useEffect } from 'react';

export default function withAuth(Component) {
return function ProtectedRoute(props) {
const { data: session, status } = useSession();
const router = useRouter();

useEffect(() => {
if (status === 'loading') return;
if (!session) router.replace('/login');
}, [session, status, router]);

if (status === 'loading') {
return <div>Loading...</div>;
}

return session ? <Component {...props} /> : null;
};
}

// Usage:
// const ProtectedPage = withAuth(YourComponent);

Server-Side Route Protection

For server components or API routes, you can check the session directly:

jsx
import { getServerSession } from 'next-auth/next';
import { authOptions } from '../api/auth/[...nextauth]/route';
import { redirect } from 'next/navigation';

export default async function ProtectedPage() {
const session = await getServerSession(authOptions);

if (!session) {
redirect('/api/auth/signin');
}

return <div>Protected content here</div>;
}

Advanced Configuration

Custom Callbacks

NextAuth.js provides several callback functions that let you control the authentication flow:

javascript
export default NextAuth({
callbacks: {
// Called after a user signs in
async signIn({ user, account, profile, email, credentials }) {
return true; // Return false to prevent sign-in
},
// Called whenever a session is checked
async session({ session, user, token }) {
// Add custom properties to the session
session.user.role = user.role;
return session;
},
// Called whenever a JWT is created or updated
async jwt({ token, user, account, profile }) {
// Add custom properties to the token
if (user) {
token.role = user.role;
}
return token;
},
},
});

Database Integration

For persistent sessions and user management, integrate with a database:

javascript
import { PrismaAdapter } from '@next-auth/prisma-adapter';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export default NextAuth({
adapter: PrismaAdapter(prisma),
// Other configuration
});

Real-World Example: Building a Protected Dashboard

Let's create a simple dashboard application with authentication:

1. Auth Configuration

javascript
// app/api/auth/[...nextauth]/route.js
import NextAuth from 'next-auth';
import GithubProvider from 'next-auth/providers/github';
import CredentialsProvider from 'next-auth/providers/credentials';
import bcrypt from 'bcrypt';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export const authOptions = {
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
CredentialsProvider({
name: 'Credentials',
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
return null;
}

const user = await prisma.user.findUnique({
where: {
email: credentials.email,
},
});

if (!user || !(await bcrypt.compare(credentials.password, user.password))) {
return null;
}

return {
id: user.id,
name: user.name,
email: user.email,
role: user.role,
};
}
}),
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.role = user.role;
}
return token;
},
async session({ session, token }) {
session.user.role = token.role;
return session;
},
},
pages: {
signIn: '/login',
},
};

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };

2. Login Page

jsx
// app/login/page.js
'use client'

import { useState } from 'react';
import { signIn } from 'next-auth/react';
import { useRouter } from 'next/navigation';

export default function LoginPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const router = useRouter();

const handleSubmit = async (e) => {
e.preventDefault();
setError('');

const result = await signIn('credentials', {
redirect: false,
email,
password,
});

if (result?.error) {
setError('Invalid email or password');
} else {
router.replace('/dashboard');
}
};

return (
<div className="login-container">
<h1>Login</h1>
{error && <p className="error">{error}</p>}
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit">Login</button>
</form>

<div className="social-login">
<p>Or sign in with:</p>
<button onClick={() => signIn('github')}>GitHub</button>
</div>
</div>
);
}

3. Dashboard Page (Protected)

jsx
// app/dashboard/page.js
import { getServerSession } from 'next-auth/next';
import { authOptions } from '../api/auth/[...nextauth]/route';
import { redirect } from 'next/navigation';
import DashboardClient from './dashboard-client';

export default async function Dashboard() {
const session = await getServerSession(authOptions);

if (!session) {
redirect('/login');
}

// Fetch user-specific data
// const userData = await fetchUserData(session.user.id);

return (
<div className="dashboard">
<h1>Welcome to your Dashboard, {session.user.name}!</h1>
<p>Role: {session.user.role}</p>
<DashboardClient session={session} />
</div>
);
}
jsx
// app/dashboard/dashboard-client.jsx
'use client'

import { signOut } from 'next-auth/react';

export default function DashboardClient({ session }) {
return (
<div className="dashboard-actions">
<h2>Your Account</h2>
<p>Email: {session.user.email}</p>
<button onClick={() => signOut({ callbackUrl: '/' })}>
Sign Out
</button>
</div>
);
}

Common Authentication Patterns

Role-Based Access Control (RBAC)

jsx
export default async function AdminPage() {
const session = await getServerSession(authOptions);

if (!session) {
redirect('/login');
}

if (session.user.role !== 'ADMIN') {
return (
<div className="unauthorized">
<h1>Unauthorized</h1>
<p>You do not have permission to access this page.</p>
</div>
);
}

return (
<div className="admin-dashboard">
<h1>Admin Dashboard</h1>
{/* Admin-specific content */}
</div>
);
}

Refreshing Sessions

javascript
// In your NextAuth configuration
callbacks: {
async session({ session, token }) {
// Always update the session from the token
session.user = token;

// Check if the session needs to be refreshed
const now = Math.floor(Date.now() / 1000);
const shouldRefresh = now > token.exp - 60 * 30; // Refresh 30 minutes before expiry

if (shouldRefresh) {
// Custom logic to refresh the token
const refreshedToken = await refreshAccessToken(token);
return {
...session,
user: refreshedToken,
expires: new Date(refreshedToken.exp * 1000).toISOString(),
};
}

return session;
},
}

Summary

NextAuth.js provides a powerful and flexible authentication solution for Next.js applications. In this guide, we've covered:

  1. Setting up NextAuth.js in your Next.js application
  2. Configuring various authentication providers (OAuth, Credentials, Email)
  3. Using sessions for client-side and server-side authentication
  4. Protecting routes and implementing role-based access control
  5. Building a real-world example with login and dashboard pages
  6. Advanced configurations like callbacks and database integration

By following the steps and examples outlined in this guide, you should be able to implement secure authentication in your Next.js application and customize it to meet your specific requirements.

Additional Resources

Exercises

  1. Implement a basic authentication system with GitHub OAuth
  2. Create a protected admin dashboard with role-based access control
  3. Add email/password registration and login functionality
  4. Implement passwordless authentication using the Email provider
  5. Create a user profile page that displays and allows updating user information


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)