Next Auth
Next.js lets you build full websites with both frontend and backend. One very important part of most websites is letting users log in. In this guide, we will learn how to add login (authentication) to your Next.js app using a tool called Next Auth.
Next Auth
Section titled “Next Auth”Next Auth is a free, ready-to-use tool that helps you add login to your Next.js app. It supports many ways to log in - like with Google, GitHub, Facebook, or even a plain email and password.
Docs: next-auth.js.org | authjs.dev | better-auth.com
Setting Up Next Auth
Section titled “Setting Up Next Auth”-
Install Next Auth: First, add the Next Auth package to your project by running one of these commands in your terminal:
Terminal window npm install next-auth# orbun add next-auth -
Configure Next Auth: Create a new file called
route.tsinside this folder:app/api/auth/[...nextauth]/. This file tells Next Auth how your login should work.// app/api/auth/[...nextauth]/route.tsimport NextAuth from 'next-auth';const handler = NextAuth({})export { handler as GET, handler as POST }; -
Add Environment Variables: Next Auth needs some secret keys to work. Create a file called
.env.localin the root of your project and add these lines:NEXTAUTH_URL=http://localhost:3000NEXTAUTH_SECRET=your-long-secret-key
Providers Setup
Section titled “Providers Setup”A “provider” is just the way someone logs in - like logging in with Google or GitHub. You set up your providers inside the route.ts file in the app/api/auth/[...nextauth] folder.
Google Provider
Section titled “Google Provider”To let users log in with Google, you need to create a project in the Google Developers Console and get a Client ID and Client Secret (these are like a username and password for your app to talk to Google).
-
Go to the Google API Console
-
Create a new project: Click “New Project”, give it a name, and create it.
-
Configure consent screen: Set up the OAuth consent screen. This is the screen users see before they log in. Fill in your app name, support email, and authorized domains. Save your changes.
-
Configure Credentials:
- Go to the “Credentials” section.
- Click “Create Credentials” and choose “OAuth 2.0 Client IDs”.
- Choose “Web application” as the type.
- Add your origin URI (e.g.,
http://localhost:3000). - Add your redirect URI (e.g.,
http://localhost:3000/api/auth/callback/google).
-
Save Credentials: After creating the credentials, you will get a Client ID and Client Secret. Add them to your
.env.localfile:GOOGLE_CLIENT_ID=your-google-client-idGOOGLE_CLIENT_SECRET=your-google-client-secret -
Configure Google Provider: Update your
route.tsfile to use the Google provider:// app/api/auth/[...nextauth]/route.tsimport NextAuth from 'next-auth';import GoogleProvider from 'next-auth/providers/google';const handler = NextAuth({providers: [GoogleProvider({clientId: process.env.GOOGLE_CLIENT_ID!,clientSecret: process.env.GOOGLE_CLIENT_SECRET!,}),],});export { handler as GET, handler as POST }; -
Now go to
http://localhost:3000/api/auth/signinin your browser. You should see a “Sign in with Google” button. Clicking it will take you to Google’s login page, and after logging in, you will be sent back to your app.
GitHub Provider
Section titled “GitHub Provider”To let users log in with GitHub, you need to create an OAuth App in your GitHub account settings and get a Client ID and Client Secret.
-
Create a new OAuth App: Click “New OAuth App” and fill in the details:
- Application name: your app name
- Homepage URL:
http://localhost:3000 - Authorization callback URL:
http://localhost:3000/api/auth/callback/github
-
Save Credentials: After creating the app, you will get a Client ID. Click “Generate a new client secret” to get the Client Secret. Add both to your
.env.localfile:GITHUB_CLIENT_ID=your-github-client-idGITHUB_CLIENT_SECRET=your-github-client-secret -
Configure GitHub Provider: Update your
route.tsfile to add the GitHub provider:// app/api/auth/[...nextauth]/route.tsimport NextAuth from 'next-auth';import GoogleProvider from 'next-auth/providers/google';import GitHubProvider from 'next-auth/providers/github';const handler = NextAuth({providers: [GoogleProvider({clientId: process.env.GOOGLE_CLIENT_ID!,clientSecret: process.env.GOOGLE_CLIENT_SECRET!,}),GitHubProvider({clientId: process.env.GITHUB_CLIENT_ID!,clientSecret: process.env.GITHUB_CLIENT_SECRET!,}),],});export { handler as GET, handler as POST }; -
Now go to
http://localhost:3000/api/auth/signin. You should see both “Sign in with Google” and “Sign in with GitHub” buttons.
Session Management
Section titled “Session Management”A “session” is how your app remembers that a user is logged in. By default, Next Auth uses something called JWT (JSON Web Token) to manage sessions. You can tell Next Auth to use JWT by adding a session option:
// app/api/auth/[...nextauth]/route.tsimport NextAuth from 'next-auth';import GoogleProvider from 'next-auth/providers/google';
const handler = NextAuth({ providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }), ], session: { strategy: "jwt", },});
export { handler as GET, handler as POST };Accessing Session in Client Components
Section titled “Accessing Session in Client Components”You can check if a user is logged in inside a client component using the useSession hook. But to use it, you first need to wrap your app with a SessionProvider.
The problem is that layout.tsx is a server component, so you cannot add SessionProvider directly in it. Instead, create a separate client component and use it in the layout:
// app/components/SessionProviderWrapper.tsx'use client';
import { SessionProvider } from 'next-auth/react';import React from 'react';
export default function SessionProviderWrapper({ children }: { children: React.ReactNode }) { return <SessionProvider>{children}</SessionProvider>;}// app/layout.tsximport SessionProviderWrapper from './components/SessionProviderWrapper';import React from 'react';
export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <SessionProviderWrapper> {children} </SessionProviderWrapper> </body> </html> );}Now you can use useSession in any client component to get the logged-in user’s info:
// app/components/UserProfile.tsx'use client';
import { useSession } from 'next-auth/react';
export default function UserProfile() { const { data: session, status } = useSession();
if (status === 'loading') { return <p>Loading...</p>; }
if (!session) { return <p>You are not logged in.</p>; }
return ( <div> <h1>Welcome, {session.user?.name}!</h1> <p>Email: {session.user?.email}</p> </div> );}Accessing Session in Server Components
Section titled “Accessing Session in Server Components”On the server side, you can use getServerSession to get the logged-in user’s info. You pass in the same authOptions you set up in your route.ts file:
// app/components/ServerUserProfile.tsximport { getServerSession } from 'next-auth/next';
export default async function ServerUserProfile() { const session = await getServerSession({ providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }), ], });
if (!session) { return <p>You are not logged in.</p>; }
return ( <div> <h1>Welcome, {session.user?.name}!</h1> <p>Email: {session.user?.email}</p> </div> );}Logging Out
Section titled “Logging Out”To log out a user, just link them to /api/auth/signout. Next Auth handles everything else for you:
// app/components/navbar.tsximport Link from 'next/link';
export default function Navbar() { return ( <nav> <Link href="/api/auth/signout">Logout</Link> </nav> );}Middleware and Protecting Routes
Section titled “Middleware and Protecting Routes”Middleware is code that runs before a page loads. You can use it to check if a user is logged in, and if not, redirect them to the login page. This is how you “protect” pages so only logged-in users can see them.
Create a middleware.ts file in the root of your project:
// middleware.tsimport { NextResponse } from 'next/server';
import middleware from 'next-auth/middleware';export default middleware;
// or
export { default } from 'next-auth/middleware';
// you can create middleware too but this middleware is already provided by next-auth// import { getServerSession } from 'next-auth/next';// import { authOptions } from './app/api/auth/[...nextauth]/route';
// export async function middleware(req: Request) {// const session = await getServerSession(authOptions);
// if (!session) {// // Redirect to the login page if the user is not logged in// return NextResponse.redirect(new URL('/api/auth/signin', req.url));// }
// // Let the request continue if the user is logged in// return NextResponse.next();// }
// If you do not provide a config, middleware will run for all routesexport const config = { matcher: ['/protected/:path*'], // Only run middleware on protected routes};The
matcherconfig tells Next.js which pages the middleware should run on. You can use:path*(zero or more pages inside that route),:path+(one or more), or:path?(zero or one). For more details, check the Next.js Middleware documentation.
Storing the user in the database
Section titled “Storing the user in the database”By default, Next Auth does not save user info to your database. But you can use an “adapter” to connect Next Auth to a database. Here we use the Prisma adapter to save user data.
Setup Adapter
Section titled “Setup Adapter”-
We already set up the database in the previous chapter. Now install the Prisma adapter for Next Auth:
Terminal window npm install @next-auth/prisma-adapter -
Import the Prisma adapter and add it to your
route.tsfile:// app/api/auth/[...nextauth]/route.tsimport NextAuth from 'next-auth';import GoogleProvider from 'next-auth/providers/google';import { PrismaAdapter } from '@next-auth/prisma-adapter';import { prisma } from "../prisma/client"; // Adjust the path to your prisma clientconst handler = NextAuth({adapter: PrismaAdapter(prisma), // Use Prisma adapterproviders: [GoogleProvider({clientId: process.env.GOOGLE_CLIENT_ID!,clientSecret: process.env.GOOGLE_CLIENT_SECRET!,}),],});export { handler as GET, handler as POST }; -
Add the required database tables to your
schema.prismafile. Next Auth needs these models to store users, sessions, and accounts. You can find the default schema in the Next Auth documentation.Here is an example of the default schema:
model Account {id String @id @default(cuid())userId String @map("user_id")type Stringprovider StringproviderAccountId String @map("provider_account_id")refresh_token String? @db.Textaccess_token String? @db.Textexpires_at Int?token_type String?scope String?id_token String? @db.Textsession_state String?user User @relation(fields: [userId], references: [id], onDelete: Cascade)@@unique([provider, providerAccountId])@@map("accounts")}model Session {id String @id @default(cuid())sessionToken String @unique @map("session_token")userId String @map("user_id")expires DateTimeuser User @relation(fields: [userId], references: [id], onDelete: Cascade)@@map("sessions")}model User {id String @id @default(cuid())name String?email String? @uniqueemailVerified DateTime? @map("email_verified")password String?image String?accounts Account[]sessions Session[]@@map("users")}model VerificationToken {identifier Stringtoken Stringexpires DateTime@@unique([identifier, token])@@map("verification_tokens")} -
After adding the models, run this command to apply the changes to your database:
Terminal window npx prisma migrate dev --name init
Login with Credentials
Section titled “Login with Credentials”This is the most common login method - where users type in their email and password. Next Auth has a built-in “credentials provider” for this.
Never save passwords as plain text in your database. Always hash (scramble) them first using a library like
bcrypt. Runnpm install bcryptorbun add bcryptto install it. Usebcrypt.hash(password, saltRounds)to hash a password andbcrypt.compare(plainPassword, hashedPassword)to check if a password is correct.
Set up the credentials provider in your route.ts file:
// app/api/auth/[...nextauth]/route.tsimport NextAuth, { NextAuthOptions } from 'next-auth';import CredentialsProvider from 'next-auth/providers/credentials';import GoogleProvider from 'next-auth/providers/google';import { PrismaAdapter } from '@next-auth/prisma-adapter';import { prisma } from "../prisma/client"; // Adjust the path to your prisma clientimport bcrypt from 'bcrypt';
export const authOptions: NextAuthOptions = { adapter: PrismaAdapter(prisma), // Use Prisma adapter providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }), CredentialsProvider({ name: 'Credentials', credentials: { email: { label: 'Email', type: 'email', placeholder: 'user@example.com' }, password: { label: 'Password', type: 'password', placeholder: 'Enter your password' }, }, async authorize(credentials) {
if (!credentials?.email || !credentials?.password) { return null; // Return null if email or password is not provided }
const user = await prisma.user.findUnique({ where: { email: credentials?.email }, });
if (user && credentials?.password && user.password != null) { // Check if the typed password matches the hashed password in the database const isValidPassword = await bcrypt.compare(credentials.password, user.password); if (isValidPassword) { return user; } } return null; // Return null if login fails } }) ], session: { strategy: "jwt", },};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };Signup
Section titled “Signup”Next Auth does not have a built-in signup feature, so you need to create your own API route for it. This route will take the user’s info, hash their password, and save them to the database.
// app/api/auth/signup/route.tsimport { NextRequest, NextResponse } from 'next/server';
import { prisma } from '../prisma/client'; // Adjust the path to your prisma clientimport bcrypt from 'bcrypt';
export async function POST(req: NextRequest) { const { email, password, name } = await req.json();
if (!email || !password || !name) { return NextResponse.json({ error: 'Missing required fields' }, { status: 400 }); }
// Check if a user with this email already exists const existingUser = await prisma.user.findUnique({ where: { email }, });
if (existingUser) { return NextResponse.json({ error: 'User already exists' }, { status: 400 }); }
// Hash the password before saving it to the database const hashedPassword = await bcrypt.hash(password, 10);
// Save the new user to the database const newUser = await prisma.user.create({ data: { email, password: hashedPassword, name, }, });
return NextResponse.json({ message: 'User created successfully', user: newUser }, { status: 201 });}Here is the signup form that sends the user’s info to the API route above:
// app/components/SignupForm.tsx"use client";
import { useState } from "react";import { useRouter } from "next/navigation";
export default function SignupForm() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [name, setName] = useState(""); const [error, setError] = useState(""); const router = useRouter();
const handelform = async (e: React.FormEvent) => { e.preventDefault(); setError("");
const response = await fetch("/api/auth/signup", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password, name }), });
const data = await response.json(); if (data.error) { setError(data.error); } else { router.push("/api/auth/signin"); // Go to the sign-in page after signup } };
return ( <form onSubmit={handelform}> <div> <label htmlFor="name">Name:</label> <input type="text" id="name" value={name} onChange={(e) => setName(e.target.value)} /> </div> <div> <label htmlFor="email">Email:</label> <input type="email" id="email" value={email} onChange={(e) => setEmail(e.target.value)} /> </div> <div> <label htmlFor="password">Password:</label> <input type="password" id="password" value={password} onChange={(e) => setPassword(e.target.value)} /> </div> {error && <p style={{ color: "red" }}>{error}</p>} <button type="submit">Sign Up</button> </form> );}