Building Secure Authentication and Authorization in Next.js
January 15, 2025
Learn how to implement secure authentication in Next.js using AuthJS, with best practices for SaaS security.

Authentication and authorization are the backbone of any SaaS business. If you mess them up, bad actors can take over accounts, steal data, or exploit your service.
Let’s go step by step on how to build a solid auth system in Next.js with AuthJS (formerly NextAuth.js). This guide keeps things practical and straightforward.
Common Auth Methods
There are different ways to handle authentication:
JWTs (JSON Web Tokens) are popular for APIs. They let the frontend send a signed token with each request. Downside? If a token gets leaked, an attacker can impersonate a user until it expires.
Sessions store user data on the server. The client gets a session ID, which they send with each request. More secure than JWTs because tokens aren’t flying around everywhere.
OAuth (Google, GitHub, Twitter logins) lets users sign in with existing accounts. Less friction for users, but third-party auth means you're depending on another service.
Which one should you use? For most SaaS apps, session-based auth with OAuth options is the best mix of security and ease of use.
Why Security Matters in Subscription Models
If you’re running a SaaS, security isn’t just about avoiding hacks. It's about keeping users’ trust.
Imagine if someone hijacks accounts and changes billing details. Now your paying customers are getting unexpected charges. Refund requests pile up. Support is overwhelmed. People cancel.
Or worse—if you store passwords insecurely and there’s a breach, your users might reuse that password elsewhere. They’ll blame you for any damage.
Strong authentication protects your revenue and reputation.
Setting Up Secure Auth in Next.js with AuthJS
Let’s implement authentication using AuthJS.
Install Dependencies
If you haven’t already, install the required packages:
npm install next-auth @auth/core
Create an Auth API Route
Next.js has a built-in API layer. Let’s create an authentication handler.
In pages/api/auth/[...nextauth].js
:
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import GitHubProvider from "next-auth/providers/github";
export default NextAuth({
providers: [
GitHubProvider({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
}),
CredentialsProvider({
name: "Credentials",
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
const user = await authenticateUser(
credentials.email,
credentials.password
);
if (user) return user;
throw new Error("Invalid credentials");
},
}),
],
session: {
strategy: "jwt",
},
callbacks: {
async session({ session, token }) {
session.user.id = token.sub;
return session;
},
},
secret: process.env.NEXTAUTH_SECRET,
});
Protecting API Routes
If you have private API routes, protect them.
Example: pages/api/protected.js
:
import { getServerSession } from "next-auth";
import authOptions from "./auth/[...nextauth]";
export default async function handler(req, res) {
const session = await getServerSession(req, res, authOptions);
if (!session) {
return res.status(401).json({ message: "Unauthorized" });
}
res.json({ message: "This is a protected route" });
}
Storing Session Tokens Securely
AuthJS lets you store sessions in a database. For production, don’t use the default JWT session strategy. Instead, set up a database adapter.
npm install @auth/prisma-adapter
Then update authOptions
:
import { PrismaAdapter } from "@auth/prisma-adapter";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export default NextAuth({
adapter: PrismaAdapter(prisma),
...
});
This way, session data is stored in the database instead of being sent as JWTs.
Best Practices for Password Management
Don’t store passwords as plain text. Always hash them. Use bcrypt:
npm install bcrypt
Hash passwords before saving:
import bcrypt from "bcrypt";
async function hashPassword(password) {
return await bcrypt.hash(password, 10);
}
When verifying:
async function verifyPassword(inputPassword, storedHash) {
return await bcrypt.compare(inputPassword, storedHash);
}
Use strong password policies. Enforce minimum length and complexity.
Email Verification
When a user signs up, send a verification email before activating their account. Use a service like Resend, SendGrid, or Postmark.
import { Resend } from "resend";
const resend = new Resend(process.env.RESEND_API_KEY);
async function sendVerificationEmail(email, token) {
await resend.send({
from: "no-reply@yourapp.com",
to: email,
subject: "Verify your email",
text: `Click this link to verify: https://yourapp.com/verify?token=${token}`,
});
}
Require users to verify before they can log in.
Wrapping Up
Security isn’t just about avoiding hacks—it’s about protecting your business.
Use session-based auth for most SaaS apps. Store sessions in a database, not just JWTs. Hash passwords and enforce strong policies. Verify user emails before allowing access.
A secure authentication system helps you build trust and keep your SaaS revenue safe.
The Next.js Starter Kit for Subscription-Based SaaS
Build and monetize your SaaS without worrying about infrastructure—auth, payments, marketing, and headless APIs are ready to go.
Related Articles
Set Up Payments for Your SaaS
Easily set up payments for your SaaS business. Learn how to choose the right tools to get started quickly.
SaaS Pricing Models Explained: Tiered, Freemium, and More
Learn about different SaaS pricing models like tiered, freemium, and usage-based pricing. Discover the pros and cons of each model and how to implement flexible pricing with FastStartup.dev.
Top 10 Features Every Successful SaaS Needs
There are core features that every SaaS should have, and skipping any of them can hurt your chances of success.