Using Passport.js LocalStrategy with Custom Form Fields in Express
Learn how to configure Passport.js LocalStrategy to work with custom login field names while keeping authentication secure.
08 Oct 2026, 23:35 UTC

Problem: Your login form uses different field names
Many applications collect an email address or a username via a field named something other than the default username that Passport’s LocalStrategy expects. If the names don’t match, the strategy receives undefined values and authentication fails, forcing you to either rename form fields or write extra middleware.
Solution: Configure LocalStrategy with usernameField and passwordField
Passport lets you override the field names through the options object passed to the strategy constructor. By specifying usernameField and passwordField you keep your existing HTML unchanged while telling Passport where to find the credentials.
Worked Example
// server.js
const express = require('express');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const session = require('express-session');
const bcrypt = require('bcrypt');
const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(session({ secret: 'change-me', resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());
// Example user store – replace with a database query in production
const users = [{ id: 1, username: 'alice', password: '$2b$10$hashedexample' }];
passport.use(new LocalStrategy(
{
usernameField: 'email', // matches
passwordField: 'pwd' // matches
},
(email, pwd, done) => {
const user = users.find(u => u.username === email);
if (!user) { return done(null, false, { message: 'Incorrect email.' }); }
bcrypt.compare(pwd, user.password, (err, isMatch) => {
if (err) { return done(err); }
if (!isMatch) { return done(null, false, { message: 'Incorrect password.' }); }
return done(null, user);
});
}
));
passport.serializeUser((user, done) => done(null, user.id));
passport.deserializeUser((id, done) => {
const user = users.find(u => u.id === id);
return done(null, user);
});
app.get('/login', (req, res) => {
res.send(`
Email:
Password:
Log in
`);
});
app.post('/login',
passport.authenticate('local', { failureRedirect: '/login' }),
(req, res) => res.redirect('/')
);
app.get('/', (req, res) => {
if (!req.isAuthenticated()) { return res.redirect('/login'); }
res.send(`Hello ${req.user.username}! Logout`);
});
app.get('/logout', (req, res, next) => {
req.logout(err => { if (err) return next(err); res.redirect('/login'); });
});
app.listen(3000, () => console.log('http://localhost:3000'));
How to Verify
- Run
node server.js(requires Node ≥ 14 andnpm install express passport passport-local bcrypt express-session). - Open
http://localhost:3000/loginand submit the form. - After login, inspect the session cookie in the browser’s developer tools (Application → Cookies). It should contain only a session identifier, not the user object.
- Add a temporary
console.log(req.user)inside any protected route to confirm that deserializeUser returned the full user object.
Trade‑off and Limitation
LocalStrategy does not hash passwords; the verify callback must compare the supplied password with a securely hashed value using bcrypt or a similar library. Skipping this step exposes plaintext credentials. Additionally, storing only the user ID in the session keeps the cookie small, but each request triggers a lookup in deserializeUser; ensure that lookup is efficient (e.g., indexed DB column) to avoid latency.
Actionable Closing
Replace the hard‑coded users array with a real database query, add proper error handling, and test both successful and failed logins. Verify the session cookie contains only an ID and monitor login latency. This gives you a secure, flexible authentication foundation that works with any custom login form.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.