Passport vs NextAuth in 2026: Express vs Next.js Authentication
·PkgPulse Team
TL;DR
Passport for Express/Node.js backends; NextAuth for Next.js apps. Passport.js (~5M weekly downloads) is the authentication middleware standard for Express — battle-tested, 500+ strategies. NextAuth/Auth.js (~2.5M downloads) was designed for Next.js route handlers but now supports multiple frameworks via Auth.js v5. If you're building an Express API, use Passport. If you're building a Next.js app, use NextAuth.
Key Takeaways
- Passport.js: ~5M weekly downloads — NextAuth/Auth.js: ~2.5M (npm, March 2026)
- Passport has 500+ strategies — OAuth, LDAP, SAML, JWT, local
- NextAuth is Next.js-first — server actions, App Router, RSC support
- Passport is framework-agnostic — Express, Fastify, Hapi, Koa
- Auth.js v5 — NextAuth expanded to support SvelteKit, SolidStart, and Nuxt
Passport.js: The Express Standard
// Passport + Express — local (email/password) strategy
const express = require('express');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const session = require('express-session');
// Configure strategy
passport.use(new LocalStrategy(
{ usernameField: 'email' },
async (email, password, done) => {
try {
const user = await User.findOne({ email });
if (!user || !await user.validatePassword(password)) {
return done(null, false, { message: 'Invalid credentials' });
}
return done(null, user);
} catch (err) {
return done(err);
}
}
));
// Serialize/deserialize user for session
passport.serializeUser((user, done) => done(null, user.id));
passport.deserializeUser(async (id, done) => {
const user = await User.findById(id);
done(null, user);
});
const app = express();
app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());
// Routes
app.post('/login', passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/login',
failureFlash: true,
}));
app.get('/protected', ensureAuthenticated, (req, res) => {
res.json({ user: req.user });
});
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) return next();
res.redirect('/login');
}
OAuth Strategies (Passport)
// Passport — Google OAuth strategy (one of 500+)
const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/auth/google/callback',
},
async (accessToken, refreshToken, profile, done) => {
let user = await User.findOne({ googleId: profile.id });
if (!user) {
user = await User.create({
googleId: profile.id,
email: profile.emails[0].value,
name: profile.displayName,
});
}
return done(null, user);
}
));
// Routes
app.get('/auth/google', passport.authenticate('google', { scope: ['email', 'profile'] }));
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
(req, res) => res.redirect('/dashboard')
);
NextAuth (Auth.js) for Next.js
// NextAuth — App Router setup (Next.js 14+)
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';
import Google from 'next-auth/providers/google';
import Credentials from 'next-auth/providers/credentials';
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
GitHub({ clientId: process.env.GITHUB_ID!, clientSecret: process.env.GITHUB_SECRET! }),
Google({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET! }),
Credentials({
credentials: { email: {}, password: {} },
authorize: async ({ email, password }) => {
return await validateUser(email as string, password as string);
},
}),
],
});
// export route handlers
export const { GET, POST } = handlers;
// Protected page — NextAuth App Router pattern
// app/dashboard/page.tsx
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>;
}
Auth.js v5 — Multi-Framework
// Auth.js v5 works across frameworks
// SvelteKit:
import { SvelteKitAuth } from '@auth/sveltekit';
import GitHub from '@auth/sveltekit/providers/github';
export const { handle } = SvelteKitAuth({ providers: [GitHub({})] });
// Nuxt:
import GitHub from '@auth/nuxt/providers/github';
export default defineNuxtAuthConfig({ providers: [GitHub({})] });
// The core stays the same across frameworks
When to Choose
Choose Passport.js when:
- Building an Express REST API or GraphQL API
- You need specialized strategies (SAML, LDAP, OpenID, etc.)
- Existing Express application
- Backend-only auth (no server-side rendering)
- Team knows Express middleware patterns
Choose NextAuth/Auth.js when:
- Building a Next.js application (the standard choice)
- Using SvelteKit, Nuxt, or SolidStart (Auth.js v5 supports these)
- You want OAuth setup in minutes, not hours
- Server-side session management via framework route handlers
- React Server Components compatibility is needed
Compare Passport and NextAuth package health on PkgPulse.
See the live comparison
View passport vs. nextauth on PkgPulse →