Next.js Authentication: The Biggest Mistakes Developers Still Make 😢
I’ve built a lot of authentication systems in Next.js over the years, I’ve seen developers repeat the same mistakes — from insecure token…
Nextjs Security ✅
Next.js Authentication: The Biggest Mistakes Developers Still Make 😢
I’ve built a lot of authentication systems in Next.js over the years, I’ve seen developers repeat the same mistakes — from insecure token handling to poor session logic. In this post, I’ll share the biggest mistakes and how to avoid them, so you can build secure, scalable, production-ready auth flows in Next.js 15.

A cover image was created using Canva.
Authentication always seems simple — until it suddenly breaks in production.
If you’ve ever tried setting up auth in Next.js using NextAuth, JWTs, or even a custom OAuth setup, you’ve probably hit some of the usual headaches,
- Sessions that don’t persist
- Users randomly getting logged out
- Tokens showing up where they shouldn’t
- Or that annoying page flicker before redirecting to the login screen
After working on multiple production apps with Next.js like SaaS dashboards, admin panels, and APIs then I noticed a pattern — developers keep making the same mistakes.
So, in this article, I’ll go through the biggest authentication mistakes in Next.js 15, explain why they happen, and show how to fix them with clean, secure solutions.
✅ Mistake 1 : Mixing Client and Server Authentication✅
This is by far the most common issue I see. Developers often rely on client-side session checks every where on the code base like this,
// ❌ Client-only session check
const { data: session } = useSession();
if (!session) router.push("/login");
This is correct, but the real problem is this is using every where. Lets look : This technique leads to that annoying “flicker” effect — the protected page flashes for a moment before unauthenticated users are redirected. Beyond being a bad user experience, it’s also a small security risk, because some private UI elements become visible, even if just for a split second.
👌 How to fix
Try to always validate authentication on the server — using middleware or server components. Like this,
// ✅ middleware.ts
import { getToken } from "next-auth/jwt";
import { NextResponse } from "next/server";
export async function middleware(req) {
const token = await getToken({ req });
if (!token) return NextResponse.redirect(new URL("/login", req.url));
return NextResponse.next();
}
Now, users without valid sessions never even reach your protected pages. No flicker. No leaks. Just clean, server-side enforcement and nothing to worry about.
✅ Mistake 2 : Storing Tokens in localStorage✅
This is a huge security risk — anyone running malicious scripts (XSS) can steal those tokens.
// ❌ Insecure example
localStorage.setItem("token", userToken);
👌 How to fix
Use httpOnly cookies. They can’t be accessed by JavaScript, making them much safer.
If you’re using NextAuth, it already handles this for you:
// NextAuth uses secure httpOnly cookies by default
No manual storage needed — just configure your providers correctly, and NextAuth will manage everything.
✅ Mistake 3 : Ignoring Environment Variables in Production✅
I can’t tell you how many “it works locally but not on Vercel(Hosting Platform)” problems I’ve come across.
Almost every time, it comes down to one thing — environment variables that weren’t set up correctly.
Typical offenders:
NEXTAUTH_SECRET=
NEXTAUTH_URL=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
Real World Examples

Screenshot of Environment Variables from VScode Editor
When these are wrong, your callbacks fail or sessions break after deploy.
👌 How to fix
- Use Vercel Environment Variables (Settings → Environment Variables)
- Don’t commit .env files to GitHub
- Always verify NEXTAUTH_URL matches your domain
If you’re testing locally, then use this:
NEXTAUTH_URL=http://localhost:3000
If you’re in production, then use this:
NEXTAUTH_URL=https://your-domain.com
✅ Mistake 4: Using “useSession()” in Server Components✅
In Next.js 15, this is a common confusion.
useSession() only works in Client Components — using it in Server Components will throw an error.
👌 How to fix
Use the new auth() helper from NextAuth v5:
import { auth } from "@/auth";
import { redirect } from "next/navigation";
export default async function Dashboard() {
const session = await auth();
if (!session) redirect("/login");
return <div>Welcome {session.user.name}</div>;
}
✅ Mistake 5: Forgetting Session Expiry and Refresh Logic✅
Users getting logged out “randomly”?
It’s rarely random — it’s almost always a missing session timeout or refresh setting.
👌 How to fix
Set explicit expiration and refresh values:
session: {
strategy: "jwt",
maxAge: 60 * 60, // 1 hour
updateAge: 15 * 60, // refresh every 15 minutes
}
This keeps your sessions alive and predictable.
✅ Mistake 6: Leaving API Routes Unprotected✅
I’ve seen apps where /api/admin or /api/user endpoints are wide open — anyone can hit them from Postman.
👌 How to fix
Protect them using getServerSession:
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
export async function GET(req) {
const session = await getServerSession(authOptions);
if (!session) return new Response("Unauthorized", { status: 401 });
return new Response("OK");
}
Now, only logged-in users can access your API endpoints.
✅ Mistake 7: Skipping Role-Based Access Control✅
Once your app grows beyond the basics, roles become essential.
Without proper role-based access, every user ends up with the same permissions — which is risky and hard to control.
👌 How to fix
Add roles to your user model like this. I’m using Prisma for the database. This is simple, scalable, and secure.
model User {
id String @id @default(cuid())
email String @unique
role String @default("user")
}
Then enforce roles in middleware:
if (token?.role !== "admin") return NextResponse.redirect("/403");
👏 Bonus👏
- Use HTTPS everywhere (even staging)
- Rotate secrets every few months
- Don’t log tokens or sessions
- Keep next-auth updated
- Use OAuth (Google, GitHub) for reliability
Authentication is the backbone of any app’s security — but it’s also one of the easiest things to mess up.
Most of the time, these problems don’t come from bad code — they come from tiny oversights that build up over time.
Fixing them not only saves you hours of debugging but also helps you protect your users and build apps that scale with confidence.
I’ve written several in-depth articles on Next.js authentication over on Medium.
If you want to dive deeper into how authentication really works, check them out and give them a follow! Click Here
Thank you all. 🙌
A message from our Founder
Hey, Sunil here. I wanted to take a moment to thank you for reading until the end and for being a part of this community.
Did you know that our team run these publications as a volunteer effort to over 3.5m monthly readers? We don’t receive any funding, we do this to support the community. ❤️
If you want to show some love, please take a moment to follow me on LinkedIn, TikTok, **Instagram. You can also subscribe to our [weekly newsletter](https://newsletter.plainenglish.io/)**.
And before you go, don’t forget to clap and follow the writer️!
메타데이터
- post_id
- 9d6795efff89
- slug
- next-js-authentication-the-biggest-mistakes-developers-still-make-9d6795efff89
- url
- https://javascript.plainenglish.io/next-js-authentication-the-biggest-mistakes-developers-still-make-9d6795efff89
- canonical_url
- https://javascript.plainenglish.io/next-js-authentication-the-biggest-mistakes-developers-still-make-9d6795efff89
- author_url
- https://medium.com/@supunjayalath7
- status
- ok
- fetched_at
- 2026-07-17 01:32:46