passport-local
semver
>=1.0.0postconditions10functions5last verified2026-04-18coverage score83%Postconditions — what we check
- Strategy · async-verify-error-handlingerrorWhenverify callback is async or returns PromiseThrows
UnhandledPromiseRejection if async operations not wrapped in try-catchRequired handlingWhen the verify callback is async or returns a Promise, all async operations (database queries, bcrypt.compare) MUST be wrapped in try-catch. Unhandled promise rejections in the verify callback will crash the Node.js application. Async errors MUST be caught and passed to done(err).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - Strategy · verify-callback-doneerrorWhenverify callback invokedThrows
Request timeout if done() not calledRequired handlingThe verify callback MUST call the done() callback in all code paths. Patterns: done(err) for server errors, done(null, user) for success, done(null, false) for authentication failures. Not calling done() leaves the request hanging, causing timeouts and resource exhaustion.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[2] - Strategy · database-error-propagationerrorWhendatabase query fails in verify callbackThrows
Database errorRequired handlingErrors from database queries (User.findOne, etc.) MUST be checked and propagated to Passport via done(err). Ignoring database errors causes silent failures and incorrect authentication behavior.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - Strategy · timing-attack-password-comparisonwarningWhenpassword comparison in verify callbackReturnsboolean indicating password matchRequired handlingPassword comparison SHOULD use constant-time functions like bcrypt.compare() instead of direct string comparison (=== or !==). Direct comparison creates timing side-channel vulnerabilities where attackers can exploit timing differences to guess passwords. CWE-208: Observable Timing Discrepancy.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[4]
- Strategy · user-enumeration-messageswarningWhenauthentication failure in verify callbackReturnsdone(null, false, { message })Required handlingDifferent error messages for "user not found" vs "wrong password" enable user enumeration attacks. Use the same generic message (e.g., "Invalid credentials") for all authentication failures. CWE-204: Observable Response Discrepancy.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[5]
- Strategy · authentication-failure-error-typewarningWhenauthentication failure (invalid credentials)Returnsdone(null, false)Required handlingAuthentication failures MUST return done(null, false), not done(new Error()). Using done(err) for invalid credentials causes HTTP 500 responses instead of proper authentication failure handling (401 or redirect). Reserve done(err) for actual server errors like database connection failures.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[6]
- Strategy (passReqToCallback) · pass-req-callback-signature-mismatcherrorWhenpassReqToCallback: true is set but verify callback uses (username, password, done) signatureThrows
Request hangs (done() never called); timeout after server request timeoutRequired handlingWhen passReqToCallback: true is set, the verify callback MUST use the signature (req, username, password, done) — NOT (username, password, done). With the wrong signature, the req object is silently bound to the username parameter, the actual username to password, and the actual password to done. The real done callback is never called, leaving the HTTP request hanging until the server connection times out. The bug is completely silent — no error is thrown, no warning is logged.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilitysilent - Strategy (body-parser dependency) · missing-body-parser-silent-400errorWhenbody-parser (express.urlencoded/express.json) middleware not mounted before passport.authenticate()Throws
HTTP 400 'Missing credentials' — not an exception, but an authentication failureRequired handlingExpress applications using passport-local MUST configure body-parser middleware before calling passport.authenticate(). Without it, req.body is undefined and LocalStrategy returns HTTP 400 'Missing credentials' even when the client sends correct credentials in the POST body. Required middleware order: 1. app.use(express.urlencoded({ extended: false })) // for HTML form submissions 2. app.use(express.json()) // for JSON API clients 3. app.use(passport.initialize()) 4. app.use(passport.session()) 5. app.post('/login', passport.authenticate('local', ...)) This failure produces no server-side error — only the client sees a 400 response. It is a silent misconfiguration that is extremely hard to debug.costmediumin prodsilent failureusers seeauthentication failurevisibilitysilent - Strategy (usernameField / passwordField) · field-name-mismatch-silent-400warningWhenform field names differ from default 'username'/'password' but options not configuredThrows
HTTP 400 'Missing credentials' — authentication silently failsRequired handlingWhen the login form uses non-default field names, LocalStrategy options MUST be explicitly configured to match: new LocalStrategy( { usernameField: 'email', passwordField: 'pass' }, function(email, pass, done) { ... } ) Without this configuration, the strategy looks for req.body.username and req.body.password, finds undefined for both, and returns HTTP 400 'Missing credentials' even though the client sent credentials in the correct field names. The mismatch produces no server-side warning — developers must trace the 400 response back through the strategy source to discover the cause.costlowin prodsilent failureusers seeauthentication failurevisibilitysilentSources[7] - serializeUser · session-serialization-requiredwarningWhenusing passport-local with sessionsThrows
Session not persistedRequired handlingUsing passport-local without implementing passport.serializeUser() and passport.deserializeUser() breaks session persistence. Users must re-authenticate on every request. This postcondition does not apply if session support is intentionally disabled (session: false option).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[2]
Sources
Every postcondition cites at least one of these. Numbered to match the footnotes above.
- [1]github.com/jaredhanson/passporthttps://github.com/jaredhanson/passport/issues/536
- [2]passportjs.org/concepts/authenticationhttps://www.passportjs.org/concepts/authentication/strategies/
- [3]moldstud.com/articles/p-best-practices-for-effective-error-handling-in-passportjshttps://moldstud.com/articles/p-best-practices-for-effective-error-handling-in-passportjs
- [4]onlinehashcrack.com/guides/password-recoveryhttps://www.onlinehashcrack.com/guides/password-recovery/timing-attacks-on-password-checks-mitigation-tips.php
- [5]onelogin.com/blog/user-enumeration-attacks-what-you-need-to-knowhttps://www.onelogin.com/blog/user-enumeration-attacks-what-you-need-to-know
- [6]github.com/jaredhanson/passport-localhttps://github.com/jaredhanson/passport-local/issues/4
- [7]github.com/jaredhanson/passport-localhttps://github.com/jaredhanson/passport-local/blob/master/lib/strategy.js
- [8]github.com/jaredhanson/passport-localhttps://github.com/jaredhanson/passport-local/issues/128
Need a different package?
Request a profile