Introduction: Authentication in Next.js Has Changed
If you haven't touched Next.js authentication since the Pages Router era, prepare for a significant upgrade. The App Router — stable since Next.js 13 and the default for new projects in 2026 — moves authentication to the server by default. That means no more getServerSideProps boilerplate, no flash of unauthenticated content, and no sensitive token-verification logic running in the browser.
But "moved to the server" doesn't mean "solved." In 2026, Next.js developers still face real questions: Should I roll my own auth? Use NextAuth (Auth.js v5)? Reach for a managed platform? What about passkeys? Enterprise SSO? Fraud protection? How does middleware fit in?
This guide answers all of those questions. We'll cover the full picture — from picking the right approach to writing your first protected Server Component — with working TypeScript code throughout. By the end, you'll have a clear mental model of authentication in the Next.js App Router and a practical path to shipping it.
What to Look for in a Next.js Auth Solution
Before comparing tools, it helps to define what "good" looks like. Think of choosing an auth solution like choosing a security system for a building: you need locks on every door (route protection), a front desk (session management), a visitor log (audit trail), and ideally a system that updates itself when new threats emerge.
Here's the checklist that matters in 2026:
- App Router compatibility — Works natively with Server Components, Route Handlers, and
next/headers. No workarounds needed. - Passkeys support — Passkeys (WebAuthn) are the phishing-resistant, passwordless future of login. If your auth solution doesn't support them today, you'll be retrofitting later.
- Social login (OAuth) — Google, GitHub, and Apple sign-in are table stakes. Users expect them.
- SSO / enterprise login — SAML and OIDC federation for enterprise customers who need to log in with their company identity provider.
- Multi-factor authentication (MFA) — TOTP apps, SMS OTP, and ideally step-up MFA for sensitive actions.
- Fraud protection — Bot detection, suspicious login alerts, and rate limiting on auth endpoints. Often overlooked until an attack happens.
- Edge runtime compatibility — Middleware runs on the Edge. Your auth token verification needs to run there too.
- TypeScript-first SDK — Type safety catches auth bugs before they reach production.
Keep this list in mind as we look at the three main approaches.
Authentication Approaches: DIY vs NextAuth vs Authgear
There are three broad approaches to authentication in Next.js. Each trades off control, complexity, and coverage differently.
| Approach | Setup time | Passkeys | SSO (SAML/OIDC) | Fraud protection | Pricing |
|---|---|---|---|---|---|
| DIY (custom) | Days–weeks | Manual (SimpleWebAuthn) | Manual | DIY | Free (but your engineering time) |
| NextAuth / Auth.js v5 | Hours | Experimental provider | Limited | None built-in | Free (OSS) |
| Authgear | Minutes | Built-in, production-ready | Full SAML + OIDC | Built-in (bot protection, suspicious logins) | Free tier, then usage-based |
DIY Authentication
Building auth from scratch gives you maximum control. You write the password hashing (bcrypt), the session store (Redis or a database), the JWT signing, and every OAuth flow yourself. You'll learn a lot — and you'll also spend weeks on work that isn't your core product.
The hidden cost of DIY is maintenance. Every new auth method (passkeys, magic links, TOTP) is a fresh project. Every security vulnerability in your custom implementation is yours to patch. For most product teams, DIY is the wrong tradeoff.
NextAuth / Auth.js v5
Auth.js v5 (the successor to NextAuth) is a solid open-source library that handles OAuth providers, email/password, and basic session management. It integrates well with the App Router via a single auth() function and supports middleware natively.
Its limitations show as your needs grow: passkeys support is still experimental, SAML SSO requires significant custom work, and there's no built-in fraud protection. For a simple consumer app, it's a sensible choice. For a B2B SaaS or any product with enterprise requirements, you'll hit its ceiling quickly.
Authgear
Authgear is an authentication platform that provides a production-ready identity layer — passkeys, social login, TOTP MFA, SAML SSO, and bot protection — through a single integration. The @authgear/nextjs SDK is built specifically for the App Router and works in Server Components, Route Handlers, and middleware without any hacks.
Think of Authgear as the difference between wiring your own electrical system versus calling a licensed electrician who also monitors your circuit breakers 24/7. The rest of this guide uses Authgear for code examples because it covers the full checklist out of the box.
Setting Up @authgear/nextjs: Quick Start
Let's get Authgear running in a Next.js 15 App Router project. This assumes you already have a Next.js app. If not, run npx create-next-app@latest --typescript.
Step 1: Create an Authgear Project
Sign up at authgear.com and create a new project. You'll get a Client ID, Client Secret, and an Endpoint (e.g., https://your-app.authgear.cloud). In the Authgear portal, add http://localhost:3000/api/auth/callback as an allowed redirect URI.
Step 2: Install the SDK
npm install @authgear/nextjs
Step 3: Add Environment Variables
Create a .env.local file in your project root:
AUTHGEAR_CLIENT_ID=your_client_id
AUTHGEAR_CLIENT_SECRET=your_client_secret
AUTHGEAR_ENDPOINT=https://your-app.authgear.cloud
AUTHGEAR_REDIRECT_URI=http://localhost:3000/api/auth/callback
SESSION_SECRET=a-long-random-string-at-least-32-chars
Step 4: Create the Config File
Create src/lib/authgear.ts. This file holds your Authgear configuration and is shared by server-side and client-side code:
// src/lib/authgear.ts
import type { AuthgearConfig } from "@authgear/nextjs";
export const authgearConfig: AuthgearConfig = {
endpoint: process.env.AUTHGEAR_ENDPOINT!,
clientID: process.env.AUTHGEAR_CLIENT_ID!,
redirectURI: process.env.AUTHGEAR_REDIRECT_URI!,
sessionSecret: process.env.SESSION_SECRET!,
};
Step 5: Set Up the Auth Route Handler
Create the catch-all API route that handles the OAuth callback, logout, session refresh, and userinfo:
// src/app/api/auth/[...authgear]/route.ts
import { createAuthgearHandlers } from "@authgear/nextjs";
import { authgearConfig } from "@/lib/authgear";
export const { GET, POST } = createAuthgearHandlers(authgearConfig);
This registers the following routes automatically:
| Method | Path | What it does |
|---|---|---|
| GET | /api/auth/login |
Starts the OAuth login flow |
| GET | /api/auth/callback |
Handles the OAuth callback and sets the session cookie |
| GET | /api/auth/logout |
Clears the session and revokes tokens |
| POST | /api/auth/refresh |
Refreshes an expired access token |
| GET | /api/auth/userinfo |
Returns the current user's info |
Step 6: Wrap Your App with AuthgearProvider
Because AuthgearProvider uses browser APIs, it must be a Client Component. Create a dedicated providers file first, then import it into your Server Component layout:
// src/app/providers.tsx
"use client";
import { AuthgearProvider } from "@authgear/nextjs/client";
export default function Providers({ children }: { children: React.ReactNode }) {
return <AuthgearProvider>{children}</AuthgearProvider>;
}
// src/app/layout.tsx
import Providers from "./providers";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Providers>
{children}
</Providers>
</body>
</html>
);
}
Why the separate file? layout.tsx is a Server Component by default. You cannot use "use client" directly in it without converting the entire layout to a Client Component — which defeats the purpose of the App Router's server-first model. A thin wrapper component like providers.tsx keeps only the browser-dependent code on the client.
Step 7: Your First Protected Page
Here's a Server Component that redirects unauthenticated users and shows a welcome message to authenticated ones:
// src/app/dashboard/page.tsx
import { currentUser } from "@authgear/nextjs/server";
import { authgearConfig } from "@/lib/authgear";
import { redirect } from "next/navigation";
export default async function DashboardPage() {
const user = await currentUser(authgearConfig);
if (!user) {
redirect("/api/auth/login");
}
return (
<main>
<h1>Welcome back, {user.name ?? user.email}!</h1>
<p>You are signed in as {user.email}.</p>
<a href="/api/auth/logout">Sign out</a>
</main>
);
}
No client-side token fetching, no loading spinners hiding unauthenticated flashes — the page simply doesn't render until the server has confirmed who the user is.
For the full setup reference, see the Authgear Next.js quickstart documentation.
Key Capabilities Out of the Box
One of the biggest advantages of a managed auth platform is what you get without writing extra code. Here's what Authgear enables as soon as your project is set up.
Passkeys
Passkeys replace passwords with cryptographic keys stored on the user's device. When a user registers, their device (phone, laptop, security key) creates a key pair. The public key goes to Authgear's servers; the private key never leaves the device. To sign in, the device proves it holds the private key using biometrics (Face ID, fingerprint, Windows Hello) — no password ever travels over the network.
In Authgear, passkeys are enabled in the portal under Authentication > Passwordless > Passkeys. No SDK changes required — the Authgear login UI handles the WebAuthn ceremony automatically.
Social Login
Google, Apple, GitHub, Facebook, and more are available as toggle-on OAuth providers in the Authgear portal. Users see a standard "Continue with Google" button without any extra SDK work on your end.
SSO for Enterprise
If you're building a B2B product, enterprise customers need to log in with their own identity providers (Okta, Azure AD, Google Workspace). Authgear supports SAML 2.0 and OIDC federation, letting each enterprise tenant connect their own IdP. You configure this in the portal; your Next.js app sees the same currentUser() interface regardless of how the user authenticated.
Multi-Factor Authentication (MFA)
TOTP (Google Authenticator, Authy), SMS OTP, and backup codes are available out of the box. You can require MFA for all users or configure step-up MFA — prompting for a second factor only when a user attempts a sensitive action like changing their payment method.
Fraud Protection
Authgear includes bot protection (CAPTCHA integration) on login and registration forms, brute-force protection via account lockout, and rate limiting on auth endpoints. These are configured in the portal — no code changes needed in your Next.js app.
Server-Side Auth: currentUser() in Server Components and Route Handlers
The App Router's server-first model means authentication happens at render time, not in a separate client-side effect. The @authgear/nextjs SDK's primary server-side function is currentUser().
currentUser() — for Server Components and Route Handlers
currentUser() reads the session cookie and returns the authenticated user object, or null if no session exists. It works in any Server Component (including nested ones deep in your component tree) and in Route Handlers. Import it from @authgear/nextjs/server and pass your config object.
// Any Server Component
import { currentUser } from "@authgear/nextjs/server";
import { authgearConfig } from "@/lib/authgear";
export default async function ProfileCard() {
const user = await currentUser(authgearConfig);
if (!user) {
return <p>Please <a href="/api/auth/login">sign in</a> to view your profile.</p>;
}
return (
<div>
<img src={user.picture ?? "/default-avatar.png"} alt={`${user.name}'s avatar`} />
<p>{user.name}</p>
<p>{user.email}</p>
</div>
);
}
Using currentUser() in Route Handlers
The same function works in Route Handlers when you need to protect an API endpoint:
// src/app/api/user/profile/route.ts
import { currentUser } from "@authgear/nextjs/server";
import { authgearConfig } from "@/lib/authgear";
import { NextResponse } from "next/server";
export async function GET() {
const user = await currentUser(authgearConfig);
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
return NextResponse.json({ user });
}
Using Auth in Server Actions
Server Actions (the App Router's mechanism for handling mutations entirely on the server) can also call currentUser() to verify identity before any write operation:
// src/app/actions/update-profile.ts
"use server";
import { currentUser } from "@authgear/nextjs/server";
import { authgearConfig } from "@/lib/authgear";
import { revalidatePath } from "next/cache";
export async function updateDisplayName(formData: FormData) {
const user = await currentUser(authgearConfig);
if (!user) {
throw new Error("You must be signed in to update your profile.");
}
const newName = formData.get("name") as string;
await fetch("https://api.yourapp.com/profile", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name: newName }),
});
revalidatePath("/dashboard/profile");
}
The "use server" directive at the top marks the file as a Server Action module. The auth check happens entirely on the server — no token ever reaches the browser.
Protecting Routes with Middleware
For blanket route protection — redirecting all unauthenticated users away from a section of your app — middleware is the right tool. It runs on the Edge before the page renders, so unauthenticated users never trigger a Server Component render.
Middleware is covered in depth in our companion article Next.js Middleware Authentication: Protecting Routes at the Edge. For setup details and the full middleware API, refer to the Authgear Next.js documentation.
One important note: middleware is your first line of defence, but you should also check authentication inside your Server Components and Route Handlers. Defence in depth means not relying on a single layer.
JWT Handling
When a user signs in, Authgear issues a short-lived JWT access token. Your Next.js app uses this token to call your backend APIs on the user's behalf. The SDK automatically refreshes the token using a longer-lived refresh token stored in an httpOnly cookie — so your users stay logged in without being prompted repeatedly.
Key points:
- Never store JWTs in
localStorage— usehttpOnlycookies (which@authgear/nextjsdoes by default). - Verify JWTs on your backend using Authgear's JWKS endpoint. Do not trust tokens that arrive without verification.
- Short-lived access tokens paired with long-lived refresh tokens is the correct pattern — it limits exposure if a token is ever leaked.
For a full walkthrough of JWT verification, rotation, and common pitfalls, see our dedicated article: Next.js JWT Authentication: Verifying and Using Tokens Securely.
Session Management
A session is the server's record that a specific browser is authenticated. Think of it like a hotel key card: the card (session cookie) gets you through the door, but the hotel's system (session store) decides whether the card is still valid.
In @authgear/nextjs, sessions are managed automatically:
- Creation: A session is created after a successful login and stored as an encrypted
httpOnlycookie. - Validation: Every call to
currentUser()validates the session. If the access token is expired, the SDK silently refreshes it using the refresh token before fetching user info. - Logout: Navigating to
/api/auth/logoutclears the session cookie and revokes the refresh token at Authgear.
For details on configuring session duration, sliding sessions, and handling concurrent sessions (e.g., a user signed in on both laptop and phone), see Next.js Session Management: Complete Guide for App Router.
Frequently Asked Questions
Can I use Authgear with the Next.js Pages Router?
Yes. The @authgear/nextjs SDK supports both App Router and Pages Router. For the Pages Router, you'll use getServerSideProps or API routes instead of Server Components, but the underlying auth functions work the same way. This guide focuses on the App Router because it's the recommended approach for new projects in 2026.
What does currentUser() return?
currentUser() returns the user's profile (name, email, picture, user ID) from the Authgear userinfo endpoint, or null if there is no active session. It also silently refreshes an expired access token before making the call, so you don't need to handle token expiry manually. Use it in any Server Component, Route Handler, or Server Action wherever you need to know who is making the request.
How do passkeys work alongside passwords?
Authgear supports both simultaneously. You can configure your portal to allow users to register a passkey as an additional or primary login method. Users with an existing password account can add a passkey from their profile settings — no re-registration required. You can also require passkeys for all new sign-ups if you want to go fully passwordless.
Is Authgear GDPR / SOC 2 compliant?
Yes. Authgear is SOC 2 Type II certified and GDPR compliant. User data residency can be configured by region. For enterprise deployments, Authgear also offers an on-premises option. See authgear.com/security for the full compliance documentation.
How do I test authentication locally?
Set AUTHGEAR_REDIRECT_URI to http://localhost:3000/api/auth/callback and add that same URL as an allowed redirect URI in the Authgear portal. The SDK works identically in local development — you'll get a real Authgear login page with all the same features (passkeys, social login, MFA) available for testing.
Conclusion
Authentication in the Next.js App Router is genuinely better architecture than it used to be. Server Components check identity at render time, middleware guards routes at the edge, and Server Actions make authenticated mutations straightforward. The framework handles the heavy lifting — the main question is what auth system sits behind it.
Building auth yourself is a significant ongoing investment. NextAuth/Auth.js gets you to a working login page quickly, but caps out before enterprise features. A managed platform like Authgear covers the full checklist — passkeys, SSO, MFA, fraud protection — from day one, and the @authgear/nextjs SDK is built specifically for the App Router.
The best time to get auth right is before you have users. The second best time is now.
Ready to add authentication to your Next.js app? Sign up for Authgear free and follow the Next.js quickstart guide to go from zero to protected routes in under 10 minutes.



