Next-auth js
Comprehensive Guide to Using NextAuth.js in Next.js Applications
NextAuth.js is an open-source authentication solution tailored for Next.js applications. It provides a flexible and secure approach to handle authentication, supporting various sign-in methods and services. This guide will walk you through the essential steps to integrate NextAuth.js into your Next.js project, including user signup, session management, route protection, and more.
Key Features of NextAuth.js
- Multiple Providers: Supports OAuth providers like Google, Facebook, GitHub, and custom providers with credentials-based authentication.
- Passwordless Authentication: Allows email-based passwordless sign-in using magic links.
- Database Integration: Can be used with or without a database. Supports popular databases like MySQL, PostgreSQL, MongoDB, and SQLite.
- Session Management: Supports JSON Web Tokens (JWT) and database sessions, with hooks and methods for managing sessions on both client and server sides.
- Security: Implements best practices including CSRF protection and secure cookie handling, and uses encrypted JWTs by default.
- Serverless Compatibility: Designed to work seamlessly with serverless environments like AWS Lambda, Vercel, and others.
Setting Up NextAuth.js
1. Install NextAuth.js
First, install the NextAuth.js package:
npm install next-auth
2. Adding a User Signup API Route
Create a file named signup.js in the pages/api/auth directory:
// pages/api/auth/signup.js import { hash } from "bcryptjs"; import { MongoClient } from "mongodb"; export default async function handler(req, res) { if (req.method !== "POST") { return res.status(405).end(); // Method Not Allowed } const { email, password } = req.body; if (!email || !password) { return res.status(400).json({ message: "Missing email or password" }); } const hashedPassword = await hash(password, 12); const client = await MongoClient.connect(process.env.MONGODB_URI); const db = client.db(); const existingUser = await db.collection("users").findOne({ email }); if (existingUser) { client.close(); return res.status(422).json({ message: "User already exists" }); } await db.collection("users").insertOne({ email, password: hashedPassword, }); client.close(); res.status(201).json({ message: "User created" }); }
3. Sending Signup Requests from the Frontend
Create a signup form component:
// components/SignupForm.js import { useState } from "react"; export default function SignupForm() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const handleSubmit = async (e) => { e.preventDefault(); const response = await fetch("/api/auth/signup", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ email, password }), }); const data = await response.json(); console.log(data); }; return ( <form onSubmit={handleSubmit}> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" required /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" required /> <button type="submit">Sign Up</button> </form> ); }
4. Adding the Credentials Auth Provider
Create a file named [...nextauth].js in the pages/api/auth directory:
// pages/api/auth/[...nextauth].js import NextAuth from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import { verify } from "bcryptjs"; import { MongoClient } from "mongodb"; export default NextAuth({ providers: [ CredentialsProvider({ name: "Credentials", credentials: { email: { label: "Email", type: "email" }, password: { label: "Password", type: "password" }, }, async authorize(credentials) { const client = await MongoClient.connect(process.env.MONGODB_URI); const db = client.db(); const user = await db .collection("users") .findOne({ email: credentials.email }); if (!user) { client.close(); throw new Error("No user found"); } const isValid = await verify(credentials.password, user.password); if (!isValid) { client.close(); throw new Error("Invalid password"); } client.close(); return { email: user.email }; }, }), ], session: { jwt: true, }, callbacks: { async jwt(token, user) { if (user) { token.email = user.email; } return token; }, async session(session, token) { session.user.email = token.email; return session; }, }, });
5. Managing Active Sessions on the Frontend
Wrap your application with SessionProvider in your _app.js or _app.tsx file:
// pages/_app.js import { SessionProvider } from "next-auth/react"; export default function App({ Component, pageProps: { session, ...pageProps }, }) { return ( <SessionProvider session={session}> <Component {...pageProps} /> </SessionProvider> ); }
6. Adding User Logout (Signout)
Create a logout button component:
// components/LogoutButton.js import { signOut } from "next-auth/react"; export default function LogoutButton() { return <button onClick={() => signOut()}>Sign Out</button>; }
7. Adding Client-Side Page Guards (Route Protection)
Use the useSession hook to protect client-side routes:
// components/ProtectedPage.js import { useSession } from "next-auth/react"; export default function ProtectedPage() { const { data: session, status } = useSession({ required: true, onUnauthenticated() { // Redirect to sign-in page window.location.href = "/api/auth/signin"; }, }); if (status === "loading") { return <p>Loading...</p>; } return <p>Welcome, {session.user.email}</p>; }
8. Adding Server-Side Page Guards
Use the getSession method for server-side protection:
// pages/protected.js import { getSession } from "next-auth/react"; export async function getServerSideProps(context) { const session = await getSession(context); if (!session) { return { redirect: { destination: "/api/auth/signin", permanent: false, }, }; } return { props: { session }, }; } export default function ProtectedPage({ session }) { return <div>Welcome, {session.user.email}</div>; }
9. Protecting the Auth Page
Redirect authenticated users away from the auth page:
// pages/auth.js import { getSession } from "next-auth/react"; export async function getServerSideProps(context) { const session = await getSession(context); if (session) { return { redirect: { destination: "/", permanent: false, }, }; } return { props: {}, }; } export default function AuthPage() { return <div>Sign In or Sign Up</div>; }
10. Adding Change Password Logic
Create a change password API route:
// pages/api/auth/change-password.js import { hash, compare } from "bcryptjs"; import { MongoClient } from "mongodb"; export default async function handler(req, res) { if (req.method !== "POST") { return res.status(405).end(); // Method Not Allowed } const { email, oldPassword, newPassword } = req.body; const client = await MongoClient.connect(process.env.MONGODB_URI); const db = client.db(); const user = await db.collection("users").findOne({ email }); if (!user) { client.close(); return res.status(404).json({ message: "User not found" }); } const isValid = await compare(oldPassword, user.password); if (!isValid) { client.close(); return res.status(403).json({ message: "Invalid old password" }); } const hashedPassword = await hash(newPassword, 12); await db .collection("users") .updateOne({ email }, { $set: { password: hashedPassword } }); client.close(); res.status(200).json({ message: "Password updated" }); }
11. Sending Change Password Request from the Frontend
Create a change password form component:
// components/ChangePasswordForm.js import { useState } from 'react'; export default function ChangePasswordForm() { const [oldPassword, setOldPassword] = useState(''); const [newPassword, setNewPassword] = useState(''); const handleSubmit = async (e) => { e.preventDefault();