Add Authentication to Next.js 15 in Under 10 Minutes
A timed walkthrough showing exactly how to add secure login, signup, and protected routes to your Next.js 15 app—no weekend debugging required.

Let's be honest: adding authentication to your app shouldn't take all weekend. You shouldn't need to read twenty blog posts, configure OAuth providers manually, or worry about security best practices while you're just trying to ship.
In this tutorial, I'm going to show you exactly how to add production-ready authentication to a Next.js 15 app in under 10 minutes. Not "eventually" or "after some configuration"—I mean working login, signup, protected routes, and user profiles in less time than it takes to grab coffee.
If you've ever spent hours debugging session cookies or wrestling with OAuth callback URLs, this guide is your antidote. We'll use modern tools that handle the complexity for you, so you can focus on building features your users actually care about.
What You'll Build
By the end of this tutorial, you'll have a complete Next.js 15 authentication system with:
- Login and signup pages with secure session management
- Protected routes that redirect unauthenticated users
- User profile display with logout functionality
- Server-side and client-side authentication checking
- Production-ready security (no shortcuts or compromises)
Everything you need to go from "npm create" to "users can sign in" in under 10 minutes.
Implementation Roadmap
Here's what we'll accomplish at each checkpoint:
| Checkpoint | Time | What You'll Have Working |
|---|---|---|
| Step 1 | 1 min | SDK installed, environment configured |
| Step 2 | 3 min | Login and signup pages functional |
| Step 3 | 5 min | Protected routes with middleware |
| Step 4 | 7 min | User dashboard with profile display |
| Step 5 | 9 min | Complete auth flow tested end-to-end |
Prerequisites (1 minute)
Before we start the timer, make sure you have:
- Node.js 18+ installed
- A Next.js 15 project (or create one now with
npx create-next-app@latest) - A free AuthHero account (sign up at [app.example.com/signup])
If you're starting from scratch:
npx create-next-app@latest my-auth-app
cd my-auth-appChoose these options when prompted:
- TypeScript: Yes
- ESLint: Yes
- Tailwind CSS: Yes
- App Router: Yes (critical for this tutorial)
Note: This tutorial requires Next.js App Router. If you're using Pages Router, check out our Pages Router authentication guide.
Timer Starts Now ⏱️
Step 1: Install the SDK (1 minute)
First, install the AuthHero SDK for Next.js:
# Install the authentication package
pnpm add @productname/nextjsCreate a .env.local file in your project root with your API keys (grab these from your AuthHero dashboard):
# Get these from dashboard.productname.com after signing up
NEXT_PUBLIC_PRODUCTNAME_DOMAIN=your-domain.productname.com
NEXT_PUBLIC_PRODUCTNAME_CLIENT_ID=your_client_id
PRODUCTNAME_CLIENT_SECRET=your_client_secretSecurity note: Never commit
.env.localto version control. Add it to your.gitignore.
Now wrap your app with the authentication provider. Open app/layout.tsx and update it:
import { AuthProvider } from '@productname/nextjs';
import './globals.css';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
{/* AuthProvider makes authentication available throughout your app */}
<AuthProvider>
{children}
</AuthProvider>
</body>
</html>
);
}⏱️ Time check: 1 minute
Step 2: Add Authentication Routes (2 minutes)
Now we'll create the login and signup pages that users will interact with.
Create a login page at app/login/page.tsx:
'use client';
import { useAuth } from '@productname/nextjs';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export default function LoginPage() {
const { login, user, isLoading } = useAuth();
const router = useRouter();
// Redirect logged-in users to dashboard
useEffect(() => {
if (user) {
router.push('/dashboard');
}
}, [user, router]);
if (isLoading) {
return <div>Loading...</div>;
}
return (
<div className="flex min-h-screen items-center justify-center">
<div className="w-full max-w-md space-y-8">
<div>
<h2 className="text-3xl font-bold">Sign in to your account</h2>
<p className="mt-2 text-sm text-gray-600">
Access your dashboard and settings
</p>
</div>
<button
onClick={() => login()}
className="w-full rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
>
Sign In
</button>
<p className="text-center text-sm">
Don't have an account?{' '}
<a href="/signup" className="text-blue-600 hover:underline">
Sign up
</a>
</p>
</div>
</div>
);
}Create a signup page at app/signup/page.tsx:
'use client';
import { useAuth } from '@productname/nextjs';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export default function SignupPage() {
const { signup, user, isLoading } = useAuth();
const router = useRouter();
// Redirect logged-in users to dashboard
useEffect(() => {
if (user) {
router.push('/dashboard');
}
}, [user, router]);
if (isLoading) {
return <div>Loading...</div>;
}
return (
<div className="flex min-h-screen items-center justify-center">
<div className="w-full max-w-md space-y-8">
<div>
<h2 className="text-3xl font-bold">Create your account</h2>
<p className="mt-2 text-sm text-gray-600">
Get started in seconds
</p>
</div>
<button
onClick={() => signup()}
className="w-full rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
>
Sign Up
</button>
<p className="text-center text-sm">
Already have an account?{' '}
<a href="/login" className="text-blue-600 hover:underline">
Sign in
</a>
</p>
</div>
</div>
);
}⏱️ Time check: 3 minutes
Step 3: Protect Routes (2 minutes)
Now let's protect routes that require authentication. This ensures only logged-in users can access sensitive pages.
Create middleware.ts in your project root (not in the app/ directory):
import { withAuth } from '@productname/nextjs/middleware';
// This middleware protects routes matching the patterns below
export default withAuth({
callbacks: {
authorized: ({ token }) => !!token,
},
});
// Specify which routes require authentication
export const config = {
matcher: ['/dashboard/:path*', '/profile/:path*', '/api/protected/:path*'],
};This middleware automatically protects any route matching the patterns above. Unauthenticated users will be redirected to /login.
For server components, you can also check auth status directly:
import { getSession } from '@productname/nextjs';
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
// Server-side auth check
const session = await getSession();
if (!session) {
redirect('/login');
}
return (
<div>
<h1>Dashboard</h1>
<p>Welcome, {session.user.email}!</p>
</div>
);
}For client components, use the useAuth hook:
'use client';
import { useAuth } from '@productname/nextjs';
export default function DashboardClient() {
const { user, isLoading } = useAuth();
if (isLoading) {
return <div>Loading...</div>;
}
if (!user) {
return <div>Please log in</div>;
}
return <div>Welcome, {user.email}!</div>;
}⏱️ Time check: 5 minutes
Step 4: Display User Info and Logout (2 minutes)
Create a dashboard page at app/dashboard/page.tsx:
'use client';
import { useAuth } from '@productname/nextjs';
import { useRouter } from 'next/navigation';
export default function DashboardPage() {
const { user, logout, isLoading } = useAuth();
const router = useRouter();
if (isLoading) {
return (
<div className="flex min-h-screen items-center justify-center">
<div>Loading...</div>
</div>
);
}
const handleLogout = async () => {
await logout();
router.push('/');
};
return (
<div className="min-h-screen p-8">
<div className="mx-auto max-w-4xl">
<div className="mb-8 flex items-center justify-between">
<h1 className="text-3xl font-bold">Dashboard</h1>
<button
onClick={handleLogout}
className="rounded-md bg-red-600 px-4 py-2 text-white hover:bg-red-700"
>
Logout
</button>
</div>
<div className="rounded-lg border bg-white p-6 shadow-sm">
<h2 className="mb-4 text-xl font-semibold">Profile Information</h2>
<div className="space-y-2">
<p>
<strong>Email:</strong> {user?.email}
</p>
<p>
<strong>User ID:</strong> {user?.id}
</p>
<p>
<strong>Name:</strong> {user?.name || 'Not set'}
</p>
</div>
</div>
<div className="mt-8 rounded-lg border bg-blue-50 p-6">
<h2 className="mb-4 text-xl font-semibold">🎉 Welcome!</h2>
<p className="text-gray-700">
You've successfully added authentication to your Next.js app.
This page is protected and only accessible to authenticated users.
</p>
</div>
</div>
</div>
);
}Create a simple navigation component at components/nav.tsx:
'use client';
import { useAuth } from '@productname/nextjs';
import Link from 'next/link';
export function Nav() {
const { user, isLoading } = useAuth();
return (
<nav className="border-b bg-white">
<div className="mx-auto flex max-w-7xl items-center justify-between p-4">
<Link href="/" className="text-xl font-bold">
My App
</Link>
<div className="flex gap-4">
{isLoading ? (
<div>Loading...</div>
) : user ? (
<>
<Link href="/dashboard" className="text-blue-600 hover:underline">
Dashboard
</Link>
</>
) : (
<>
<Link href="/login" className="text-blue-600 hover:underline">
Login
</Link>
<Link
href="/signup"
className="rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
>
Sign Up
</Link>
</>
)}
</div>
</div>
</nav>
);
}⏱️ Time check: 7 minutes
Step 5: Test It (2 minutes)
Start your development server:
pnpm devOpen http://localhost:3000 and test the complete authentication flow:
✅ Testing Checklist:
- [ ] Navigate to `/signup` - Create a new account
- [ ] Automatic redirect to `/dashboard` - See your user info displayed
- [ ] Try accessing `/dashboard` in incognito window - Should redirect to login
- [ ] Click the logout button - Should redirect to home page
- [ ] Navigate to `/login` - Sign back in with your account
- [ ] Session persists on page refresh - Reload
/dashboard, should stay logged in
Everything working? Congratulations! You just added production-ready authentication to Next.js in under 10 minutes.
⏱️ Time check: 9 minutes
What You've Built
In less than 10 minutes, you now have a complete authentication system with:
- Secure session management - HttpOnly cookies, CSRF protection, automatic session refresh
- Protected routes - Server-side and client-side route protection with middleware
- Login and signup flows - Complete authentication flows with automatic redirects
- User profile display - Access to user data throughout your app
- Logout functionality - Clean session termination and redirect
And here's the best part: this isn't a prototype or proof-of-concept. This is production-ready code that handles:
- ✅ Password hashing with bcrypt
- ✅ Session token rotation
- ✅ CSRF protection
- ✅ Rate limiting on auth endpoints
- ✅ Secure password reset flows
- ✅ Email verification (if enabled)
Next Steps: Level Up Your Auth
Now that you have the basics working, here are some optional enhancements to make your authentication even better.
Add Social Login (5 more minutes)
Enable Google and GitHub login in your AuthHero dashboard, then update your login button:
<div className="space-y-3">
<button onClick={() => login({ provider: 'google' })}>
Sign in with Google
</button>
<button onClick={() => login({ provider: 'github' })}>
Sign in with GitHub
</button>
<button onClick={() => login()}>
Sign in with Email
</button>
</div>Enable Multi-Factor Authentication (2 minutes)
In your AuthHero dashboard, enable MFA under Security Settings. Users will automatically be prompted to set up 2FA on their next login. No code changes required.
Add Organizations/Teams (10 minutes)
For B2B apps, add organization support so users can belong to teams:
import { useOrganizations } from '@productname/nextjs';
export function TeamSwitcher() {
const { organizations, current, setCurrent } = useOrganizations();
return (
<select onChange={(e) => setCurrent(e.target.value)} value={current?.id}>
{organizations.map(org => (
<option key={org.id} value={org.id}>
{org.name}
</option>
))}
</select>
);
}Customize the UI
The examples above use simple styled buttons, but you can fully customize the authentication UI to match your brand. AuthHero provides unstyled components that accept your own CSS, or you can build completely custom forms.
Troubleshooting Common Issues
Hit a snag? Here are solutions to the most common problems:
| Issue | Solution |
|---|---|
| "Environment variables not found" | Ensure .env.local is in project root (not in app/ folder). Restart dev server after adding environment variables. |
| Users not redirected after login | Check that <AuthProvider> wraps your app in app/layout.tsx. Verify the provider is imported correctly. |
| Middleware not protecting routes | Confirm middleware.ts is in project root (not app/ directory). Check that matcher patterns include your protected routes. |
| Session not persisting on refresh | Use localhost not 127.0.0.1 during development. Check for conflicting middleware or cookie settings. |
| TypeScript errors with useAuth | Ensure @productname/nextjs is installed. Run pnpm install and restart your TypeScript server. |
| "Cannot read property of undefined" | Add null checks: {user?.email} instead of {user.email}. The useAuth hook returns null before loading completes. |
Still stuck?
- Check the Link: AuthHero Next.js documentation
- Join our Link: Discord community for live help
- Email support at support@example.com
Why This Works So Well
You might be wondering: how is this so fast compared to other authentication solutions?
No configuration sprawl. We made opinionated decisions about security best practices so you don't have to research OWASP guidelines at 2am. Secure defaults, zero config.
No provider setup. Want to add Google login later? Flip a switch in the dashboard. No OAuth app configuration, no callback URL management, no JSON credential files.
No database setup. We handle user storage, session management, and password security. Your database schema stays clean and focused on your product's data.
No security debt. This isn't a shortcut—you get production-grade security from minute one. We handle the hard stuff (password hashing, session rotation, CSRF tokens, rate limiting) so you can focus on your product.
The result? Authentication that "just works" without the usual weekend of debugging.
The 10-Minute Challenge
I challenged myself to keep this tutorial under 10 minutes because I was tired of seeing "quick start" guides that take hours. Time yourself following this tutorial—if it takes longer than 10 minutes, let me know and I'll improve it.
And if you finish faster? Share your time on Twitter with #10MinuteAuth. Let's prove that authentication doesn't have to be painful.
Get Started
Ready to add authentication to your Next.js app? Sign up for a free AuthHero account (10,000 monthly active users included) and follow this tutorial. No credit card required.
Get your API keys and start building →
For other frameworks, check out our Link: authentication guides for React, Vue, and more.
Quick Links: