Back to Blog
May 10, 20266 minDevFlare Team

Production-Grade Auth with NextAuth.js

A practical walkthrough of setting up credentials + OAuth authentication with NextAuth v5, Prisma, and Postgres — including session management, role-based access, and security defaults.

AuthNextAuthSecurityPrisma

Auth is not a feature

Every project we ship needs authentication. Rather than rebuilding it each time, we have a standard auth layer built on NextAuth.js v5 (Auth.js) that we drop into every project. Here's what it includes.

The stack

  • NextAuth.js v5 — the framework-agnostic auth library
  • Prisma adapter — sessions and accounts stored in Postgres
  • Credentials provider — email + password with bcrypt
  • OAuth providers — GitHub and Google as optional extras
  • Role-based access — user and admin roles on the session

Key decisions

Credentials + OAuth, not just OAuth

Many tutorials show OAuth-only setups. In practice, our clients want email/password as the primary flow with OAuth as an option. NextAuth's credentials provider works well here — just make sure you're hashing passwords with bcrypt (not argon2, not scrypt) for maximum compatibility.

Database sessions, not JWT

JWTs are stateless and fast, but you can't revoke them. For a platform where users manage projects and settings, database sessions let us invalidate all sessions on password change and give admins the ability to force-logout users. The latency cost of a database read per request is negligible.

The session callback

We extend the session with the user's role and id:

ts
callbacks: {
  session({ session, user }) {
    session.user.id = user.id;
    session.user.role = user.role;
    return session;
  },
},

This lets any server component or API route check session.user.role === "admin" without a database call.

Security defaults

  • Rate limiting on login routes (5 attempts per minute per IP)
  • bcrypt cost factor of 12
  • Session expiry at 7 days with sliding window
  • Admin routes check role in middleware, not just in the page

The middleware pattern

ts
export { auth as middleware } from class="tok-string">"@/lib/auth/config";
export const config = {
  matcher: [class="tok-string">"/dashboard/:path*", class="tok-string">"/api/admin/:path*"],
};

This redirects unauthenticated users to login and checks admin role for admin routes. One file, zero config per route.

DF
DevFlare Team
DevFlare — software studio
Work with us

// Contact

Want to talk?

Reach out about products, partnerships, or anything else. We read every message.