Skip to main content

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.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.