Secure Authentication in Next.js 15: Implementing Auth.js (NextAuth v5) with JWT & OAuth
Implement production authentication in Next.js 15 App Router using Auth.js (NextAuth v5). Learn OAuth providers, session callbacks, role-based route protection, and secure cookies.

- 1.Modern Web Authentication with Auth.js (NextAuth v5)
- 2.1. Setting Up Auth.js Configuration (`auth.config.ts`)
- 3.2. Initializing Handlers (`auth.ts`)
- 4.3. Protecting Routes with Edge Middleware (`middleware.ts`)
- 5.4. Accessing the Session in React Server Components
- 6.5. Security Best Practices Checklist
- 7.6. Real-World Sign-In Component with Social Buttons
Modern Web Authentication with Auth.js (NextAuth v5)#
Authentication is the security backbone of any modern web application. In the Next.js App Router, authentication must work seamlessly across Server Components, Server Actions, Route Handlers, and Edge Middleware.
Auth.js (formerly NextAuth.js v5) has been completely rewritten from the ground up for Next.js 15 and universal JavaScript runtimes, providing lightweight OAuth (Google, GitHub), magic links, credential authentication, and encrypted JWT session cookies with zero client-side overhead.
1. Setting Up Auth.js Configuration (`auth.config.ts`)#
Separate your edge-compatible routing rules from database adapters:
import type { NextAuthConfig } from "next-auth";
import GitHub from "next-auth/providers/github";
import Google from "next-auth/providers/google";
export const authConfig: NextAuthConfig = {
providers: [
GitHub({
clientId: process.env.AUTH_GITHUB_ID,
clientSecret: process.env.AUTH_GITHUB_SECRET,
}),
Google({
clientId: process.env.AUTH_GOOGLE_ID,
clientSecret: process.env.AUTH_GOOGLE_SECRET,
}),
],
pages: {
signIn: "/login", // Custom styled login page
error: "/auth-error",
},
callbacks: {
// 1. Authorize route access in Middleware
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isOnDashboard = nextUrl.pathname.startsWith("/dashboard");
if (isOnDashboard) {
if (isLoggedIn) return true;
return false; // Redirect unauthenticated users to /login
}
return true;
},
// 2. Attach user role and ID to JWT
async jwt({ token, user }) {
if (user) {
token.id = user.id;
token.role = (user as any).role || "USER";
}
return token;
},
// 3. Expose claims to client/server session
async session({ session, token }) {
if (session.user) {
session.user.id = token.id as string;
(session.user as any).role = token.role as string;
}
return session;
},
},
};2. Initializing Handlers (`auth.ts`)#
import NextAuth from "next-auth";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { db } from "@/lib/prisma";
import { authConfig } from "./auth.config";
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(db),
session: { strategy: "jwt" }, // High-speed stateless JWT sessions
...authConfig,
});Create the API route handler at app/api/auth/[...nextauth]/route.ts:
import { handlers } from "@/auth";
export const { GET, POST } = handlers;3. Protecting Routes with Edge Middleware (`middleware.ts`)#
Protect private routes at the edge before requests ever hit your server components:
import NextAuth from "next-auth";
import { authConfig } from "./auth.config";
export default NextAuth(authConfig).auth;
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};4. Accessing the Session in React Server Components#
In Next.js 15 Server Components, accessing the current authenticated user is an asynchronous one-liner:
import { auth } from "@/auth";
import { redirect } from "next/navigation";
export default async function DashboardPage() {
const session = await auth();
if (!session?.user) {
redirect("/login");
}
return (
<div className="p-8 text-white">
<h1 className="text-2xl font-bold">Welcome back, {session.user.name}!</h1>
<p className="text-white/60 text-sm">Role: {(session.user as any).role}</p>
</div>
);
}5. Security Best Practices Checklist#
- [ ] Store
AUTH_SECRETgenerated vianpx auth secretin production environment variables. - [ ] Use
HttpOnly,Secure, andSameSite=Laxcookies (configured automatically by Auth.js). - [ ] Implement CSRF token validation on all credential mutation forms.
- [ ] Avoid storing sensitive private tokens (like OAuth refresh tokens) in client-accessible session objects.
6. Real-World Sign-In Component with Social Buttons#
Here is how to create a clean, accessible sign-in UI with GitHub and Google OAuth triggers using Server Actions:
import { signIn } from "@/auth";
export function SignInButtons() {
return (
<div className="flex flex-col gap-4 max-w-sm mx-auto">
<form
action={async () => {
"use server";
await signIn("github", { redirectTo: "/dashboard" });
}}
>
<button
type="submit"
className="w-full py-3 px-4 rounded-xl bg-white/10 hover:bg-white/15 border border-white/10 text-white font-semibold text-sm transition-all"
>
Sign In with GitHub
</button>
</form>
<form
action={async () => {
"use server";
await signIn("google", { redirectTo: "/dashboard" });
}}
>
<button
type="submit"
className="w-full py-3 px-4 rounded-xl bg-brand-teal text-white font-semibold text-sm hover:bg-brand-teal/90 transition-all shadow-lg shadow-brand-teal/20"
>
Sign In with Google
</button>
</form>
</div>
);
}
Published by
Vyuhantrix Team
Security & Full-Stack · Vyuhantrix
Vyuhantrix is an open technology learning platform based in Ahmedabad, India, publishing step-by-step programming tutorials, system design breakdowns, and free developer tools.
Keep Learning
Recommended Guides
The Definitive Full-Stack Web Development Roadmap (2026 Edition)
A complete step-by-step masterclass covering modern HTML5/CSS, TypeScript, Next.js App Router, Server Components, API Design, and Cloud Edge Deployments.
Mastering React Server Components in Next.js 15: A Complete Guide
A deep dive into React Server Components, how they differ from Client Components, and how Next.js 15 leverages them to achieve zero-bundle-size rendering, streaming, and superior Core Web Vitals.
Next.js Server Actions: Complete Guide to Full-Stack Mutations in 2026
A comprehensive guide to Next.js Server Actions — how they work, form handling, progressive enhancement, optimistic updates, error boundaries, and integrating with databases and external APIs without exposing API routes.