Next.js Authentication in 2026: What Actually Matters
Auth is a moving target. Here's a 2026-ready checklist for production Next.js apps: passwords, sessions, OAuth, MFA, and the things you can skip.
If you’ve been building Next.js apps for more than a year, you’ve probably seen three or four “best practices” articles on authentication. Most of them are out of date the day they’re written. This one won’t be — it focuses on the things that don’t change.
The five things that don’t change
1. Argon2id, not bcrypt
Bcrypt was fine in 2010. It’s not fine in 2026. The memory hardness of Argon2id is what makes password cracking expensive, and it’s now the OWASP recommendation.
const HASH_OPTIONS = {
type: 2 as const, // argon2id
memoryCost: 19_456, // 19 MB
timeCost: 2,
parallelism: 1,
};
If you’re starting a new project today, use Argon2id. If you’re on bcrypt, the migration is straightforward — needsRehash() runs on every successful login and rolls users up over time.
2. Sessions live in the database
JWTs are stateless. Stateless is great for transport and terrible for revocation. If a user clicks “log out everywhere” and your session is only in the cookie, you have nothing to revoke.
The pattern that works:
// Cookie carries { id, role, sessionId }
// sessionId points to a row in user_sessions
// On every request, check that row is still valid
if (token.sessionId) {
const valid = await touchUserSession(token.sessionId);
if (!valid) return {} as typeof token; // forces re-login
}
The cookie is the bearer token. The database is the truth. Always.
3. HttpOnly cookies, not localStorage
localStorage is readable by any JavaScript on the page. If a user has an XSS vulnerability — in your code, in a third-party script, in a browser extension — an attacker can read their session token.
HttpOnly cookies are unreadable from JavaScript. The browser sends them on every request automatically. The browser never exposes them to scripts.
Don’t ever store auth tokens in localStorage. Don’t store them in sessionStorage. Don’t store them anywhere JavaScript can read.
4. CSRF protection on every state-changing request
Cookies are sent automatically. That’s the point — and the problem. An attacker can put a hidden form on their site, point its action at your /api/transfer-money, and your logged-in user’s browser will send the request with the auth cookie attached.
CSRF tokens fix this. SameSite=Strict cookies also fix this. Use both.
5. Email verification before access
If you let unverified users into your app, you’re letting throwaway email addresses into your database. You can’t email them. You can’t recover their accounts. They’re noise.
The fix: gate the dashboard behind a verified email. Allow sign-up, allow email change, but require the user to click the link before they can do anything else.
The five things you can probably skip
This is the part where most “best practices” articles get it wrong. They tell you to do everything, and you spend three weeks implementing rate limiting for an endpoint that gets 12 requests a day.
1. Password complexity rules
NIST 800-63B dropped complexity rules in 2017. The current recommendation: minimum length (12+ characters), check against known breached passwords, no forced character classes. Done.
2. Periodic password rotation
The same NIST guidance: don’t force users to change passwords. Forced rotation produces weaker passwords. The exception: rotation after a known breach.
3. SMS-based MFA
SMS is not secure. SIM swapping is real. SS7 attacks are real. If you need MFA, use TOTP (authenticator app) or WebAuthn (security key, Touch ID, Windows Hello). Skip SMS unless your compliance regime requires it.
4. Custom OAuth provider
You’re not going to write a better OAuth implementation than Auth.js, NextAuth, or Clerk. The time you spend on it is time you don’t spend on the product. Use a library.
5. Roll-your-own crypto
Use libraries. Use audited libraries. Use libraries that have been audited by people who have read more cryptography than you. Don’t import crypto and think you’ve solved something.
The one thing you should obsess over
Defense in depth.
Your auth is not one thing. It’s twelve things, and any one of them can fail:
- Password hashing
- Session storage
- Cookie attributes
- CSRF tokens
- Email verification
- Rate limiting (yes, on auth)
- Account lockout
- Audit logging
- Secret rotation
- Dependency updates
- Penetration testing
- Monitoring
The list isn’t a checklist. It’s a posture. The goal isn’t to do each item once. The goal is to do all of them, forever, and to make it cheap to keep doing them as your team and your product grow.
That’s what production-grade actually means. Not “we did it once.” But “we keep doing it, and the system makes it easy.”