mrkeyoor.com_
Wed 05 Aug 05:05 UTC
npmSecurityupdated 05 Aug 2026

passport

Passport is Express-compatible authentication middleware for Node.js. Its sole job is authenticating requests, which it does through plug-in packages called strategies: over 480 exist, covering username/password, OAuth providers like Google and Facebook, SAML, OpenID and JWT. It deliberately does not mount routes, manage users, hash passwords or assume a database; you supply verify callbacks and session serialization yourself.

Verdict

Still everywhere (8M weekly downloads) but effectively in maintenance mode: fine to keep in an existing Express app, hard to recommend for anything new. The strategy ecosystem was the selling point in 2015; in 2026 the same breadth exists in actively developed alternatives with far less wiring.

API stability4/5The API has barely moved in a decade, which cuts both ways: nothing breaks, but it is still versioned 0.x after 15 years and 0.6.0 changed the req.logout signature in a way that broke most existing guides.
Docs2/5passportjs.org covers happy paths thinly, core concepts like the session flow are under-explained, and strategy docs vary wildly; most real learning happens through third-party tutorials of mixed accuracy.
Maintenance2/5Last push to the repo August 2024, last release November 2023, roughly 400 open issues and PRs; the author maintains many strategy packages solo and activity across them is sparse.
Ecosystem4/5480+ strategies and a huge installed base remain the draw, but a large share of those strategies are themselves stale, so breadth on paper overstates what is safe to depend on.

Use it if

  • You maintain an existing Express app that already uses Passport and swapping auth systems is not worth the risk
  • You need a niche identity provider whose only maintained Node integration is a Passport strategy
  • You have a classic server-rendered Express app with session cookies and want the pattern with fifteen years of Stack Overflow answers behind it
Skip it if

Setup reality

npm install passport is just the start: you need one package per strategy (passport-local, passport-google-oauth20, ...), express-session with a production session store, hand-written serializeUser/deserializeUser, and your own verify callback with your own password hashing. Failure modes are famously opaque: forget the session middleware order and you get silent redirect loops with no error. req.logout became async in 0.6.0 and requires a callback, which broke a decade of tutorials that still rank first in search results.

Patterns

Username and password verificationlocal-strategy

const passport = require('passport');
const LocalStrategy = require('passport-local');

passport.use(new LocalStrategy(async (username, password, done) => {
  try {
    const user = await User.findOne({ username });
    if (!user) return done(null, false, { message: 'No user' });
    const ok = await bcrypt.compare(password, user.hash);
    return ok ? done(null, user) : done(null, false, { message: 'Bad password' });
  } catch (err) {
    return done(err);
  }
}));

passport-local is a separate package and does zero hashing; done(null, false) means auth failed, done(err) means server error, mixing them up leaks 500s to users.

Store the user in the sessionsession-serialization

passport.serializeUser((user, done) => {
  done(null, user.id);
});

passport.deserializeUser(async (id, done) => {
  try {
    done(null, await User.findById(id));
  } catch (err) {
    done(err);
  }
});

deserializeUser runs a lookup on every request with a session; cache it or keep it cheap or your database eats one query per page load.

Wire Passport into Expressmiddleware-setup

const session = require('express-session');

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  store: sessionStore,
}));
app.use(passport.initialize());
app.use(passport.session());

Order is everything: express-session must come before passport.session(), and the default MemoryStore leaks memory and drops sessions on restart, so use a real store in production.

Authenticate a login POSTlogin-route

app.post('/login',
  passport.authenticate('local', {
    successRedirect: '/dashboard',
    failureRedirect: '/login',
  })
);

Needs body parsing middleware (express.urlencoded or express.json) before it, or the strategy silently never sees credentials and you just get the failure redirect.

Guard routes behind loginprotect-route

function ensureAuth(req, res, next) {
  if (req.isAuthenticated()) return next();
  res.redirect('/login');
}

app.get('/dashboard', ensureAuth, (req, res) => {
  res.render('dashboard', { user: req.user });
});

There is no built-in guard middleware; everyone writes this same function or pulls in connect-ensure-login.

Log the user outlogout

app.post('/logout', (req, res, next) => {
  req.logout((err) => {
    if (err) return next(err);
    res.redirect('/');
  });
});

Since 0.6.0 req.logout is async and requires the callback; the old synchronous req.logout() from most tutorials throws.

Stateless API auth with JWTjwt-strategy

const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');

passport.use(new JwtStrategy({
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: process.env.JWT_SECRET,
}, (payload, done) => done(null, { id: payload.sub })));

app.get('/api/me',
  passport.authenticate('jwt', { session: false }),
  (req, res) => res.json(req.user)
);

Pass session: false for token APIs or Passport still tries to serialize into a session that APIs do not have.

Sign in with Googlegoogle-oauth

const GoogleStrategy = require('passport-google-oauth20');

passport.use(new GoogleStrategy({
  clientID: process.env.GOOGLE_CLIENT_ID,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
  callbackURL: '/auth/google/callback',
}, (accessToken, refreshToken, profile, done) => {
  User.findOrCreate({ googleId: profile.id }).then(
    (user) => done(null, user),
    (err) => done(err)
  );
}));

app.get('/auth/google', passport.authenticate('google', { scope: ['profile', 'email'] }));
app.get('/auth/google/callback',
  passport.authenticate('google', { failureRedirect: '/login' }),
  (req, res) => res.redirect('/')
);

The callbackURL must exactly match what is registered in the Google console, including protocol and port; mismatch errors surface on Google's side, not yours.

Handle auth results yourself (JSON APIs)custom-callback

app.post('/login', (req, res, next) => {
  passport.authenticate('local', (err, user, info) => {
    if (err) return next(err);
    if (!user) return res.status(401).json({ error: info.message });
    req.logIn(user, (err) => {
      if (err) return next(err);
      return res.json({ id: user.id });
    });
  })(req, res, next);
});

With a custom callback Passport no longer establishes the session for you; forgetting the manual req.logIn call is the classic bug.

Show why login failedflash-failure-messages

app.post('/login',
  passport.authenticate('local', {
    failureRedirect: '/login',
    failureMessage: true,
  })
);

// in the /login handler:
// req.session.messages holds the strategy's failure messages

failureMessage (0.6+) stores messages in req.session.messages; the older failureFlash option needs the abandoned connect-flash package.

Alternatives

PackageRegistryPick it when
better-authnpmNew TypeScript project that wants full auth (email, OAuth, 2FA, orgs) as a framework, not a middleware kit
next-authnpmNext.js app; Auth.js integrates with route handlers and edge runtimes where Passport does not fit
openid-clientnpmYou want certified, low-level OpenID Connect flows and are comfortable wiring sessions yourself