🔐 How to Set Up Authentication in Next.js 15 with Server Actions
Authentication is one of the most important parts of any modern web application.
Whether you’re building a portfolio, SaaS platform, or e-commerce app, you’ll need a way for users to securely sign up, log in, and manage their sessions.
In this guide, we’ll set up authentication in Next.js 15 (App Router) using modern best practices — no external auth libraries required.
We’ll use:
- TypeScript for type safety
- Server Actions for form handling
- Cookies/Sessions for authentication state
- Tailwind CSS for styling
By the end, you’ll have a fully functional authentication system with:
✅ User Registration
✅ Login & Logout
✅ Protected Routes
❓ Why Authentication Matters
Before we dive into code, let’s understand why authentication is essential for every modern app:
- Keeps user data secure — protects sensitive information from unauthorized access.
- Enables personalized experiences — dashboards, user settings, and custom content.
- Builds trust — users feel safe knowing their data is handled responsibly.
- Foundation for advanced features — payments, subscriptions, and role-based access all depend on a solid auth system.
1️⃣ Step 1: Setting Up the Next.js Project
Let’s start by creating a new Next.js 15 project configured with TypeScript and Tailwind CSS.
npx create-next-app@latest my-auth-app --typescript --tailwind
cd my-auth-app
Copy the commands above into your terminal — this will give you a fresh Next.js 15 setup with the App Router enabled.
2️⃣ Step 2: Creating the Database Model
For simplicity, we’ll use Prisma with SQLite (you can easily switch to PostgreSQL or MySQL later).
Install Prisma
Run the following commands:
npm install prisma @prisma/client
npx prisma init --datasource-provider sqlite
Update Your Prisma Schema
Open your prisma/schema.prisma file and update it with the following model definition:
model User {
id Int @id @default(autoincrement())
email String @unique
password String
createdAt DateTime @default(now())
}
Then migrate:
npx prisma migrate dev --name init
Don't worry if you want to know more about Prisma, I will cover that in another blog soon
3️⃣ Step 3: Creating the Registration Page
We’ll use Next.js Server Actions for form submissions.
app/register/page.tsx
"use client";
import { useState } from "react";
export default function RegisterPage() {
const [loading, setLoading] = useState(false);
async function handleRegister(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
const formData = new FormData(e.currentTarget);
await fetch("/api/register", {
method: "POST",
body: formData,
});
setLoading(false);
}
return (
<form onSubmit={handleRegister} className="max-w-sm mx-auto mt-10">
<input
type="email"
name="email"
placeholder="Email"
required
className="w-full mb-3 px-3 py-2 border rounded"
/>
<input
type="password"
name="password"
placeholder="Password"
required
className="w-full mb-3 px-3 py-2 border rounded"
/>
<button
type="submit"
disabled={loading}
className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700"
>
{loading ? "Registering..." : "Register"}
</button>
</form>
);
}
4️⃣ Step 4: Creating the API Route for Registration
app/api/register/route.ts
import { NextResponse } from "next/server";
import bcrypt from "bcrypt";
import { prisma } from "@/lib/prisma";
export async function POST(req: Request) {
const formData = await req.formData();
const email = formData.get("email") as string;
const password = formData.get("password") as string;
const hashedPassword = await bcrypt.hash(password, 10);
await prisma.user.create({
data: { email, password: hashedPassword },
});
return NextResponse.json({ success: true });
}
5️⃣ Step 5: Login & Session Handling
We’ll use cookies to keep users logged in.
**app/api/login/route.ts**
import { NextResponse } from "next/server";
import bcrypt from "bcrypt";
import prisma from "@/lib/prisma";
import { cookies } from "next/headers";
export async function POST(req: Request) {
const formData = await req.formData();
const email = formData.get("email") as string;
const password = formData.get("password") as string;
const user = await prisma.user.findUnique({ where: { email } });
if (!user) return NextResponse.json({ error: "Invalid credentials" }, { status: 401 });
const valid = await bcrypt.compare(password, user.password);
if (!valid) return NextResponse.json({ error: "Invalid credentials" }, { status: 401 });
// Await the cookies() call
const cookiesStore = await cookies();
cookiesStore.set("user", JSON.stringify({ id: user.id, email: user.email }), {
httpOnly: true,
path: "/",
secure: process.env.NODE_ENV === "production",
sameSite: "lax"
});
return NextResponse.json({ success: true });
}
6️⃣ Step 6: Protecting Routes
We can read cookies inside server components:
import { cookies } from "next/headers";
import Link from "next/link";
export default async function DashboardPage() {
const cookiesStore = await cookies()
const user = cookiesStore.get("user");
if (!user) {
return (
You must log in to view this page.
Go to Login
);
}
return (
Welcome back!
{JSON.parse(user.value).email}
);
}
7️⃣ Step 7: Logout
We can read cookies inside server components:
// app/api/logout/route.ts
import { NextResponse } from "next/server";
import { cookies } from "next/headers";
export async function POST() {
const cookiesStore = await cookies()
cookiesStore.delete("user");
return NextResponse.json({ success: true });
}
🏁 Wrapping Up
And that’s it 🎉 — you’ve just built a simple authentication system in Next.js 15 using the App Router.
Through this project, you’ve learned:
- ⚡ How powerful Server Actions and API Routes are in Next.js 15
- 🧠 How TypeScript makes authentication logic safer and easier to maintain
- 🎨 How Tailwind CSS helps you build clean, responsive forms quickly
- ☁️ How seamless Vercel deployments are with modern Next.js setups
This setup is perfect for learning and small-scale projects.
💡 For production-grade apps, consider using NextAuth.js (now Auth.js) — it provides ready-made solutions for:
- OAuth (Google, GitHub, etc.)
- JWTs and sessions
- Advanced security features
With this foundation in place, you’re well on your way to building secure, scalable full-stack applications with Next.js 15.
Read the Full Article
This is a summary of my comprehensive guide. Read the full article with code examples and project ideas on my blog:
👉 How to Set Up Authentication in Next.js 15 with Server Actions
More tutorials on my blog:
Connect with me:
- 🌐 Website: oyetech.vercel.app
- 💼 LinkedIn: [http://linkedin.com/in/oyekola-abdulqobid-bolaji-999490271]
- 🐦 Twitter: [@OyekolaAbdulqo1]
- 💻 GitHub: [https://github.com/Oyetech3]
Questions?
What's your biggest challenge in learning full-stack development? Drop a comment below!
Tags: #webdev #nextjs #authentication #tutorial
Top comments (0)