Passport.js is a popular authentication middleware for Node.js. It provides a simple and flexible way to handle authentication strategies, including local authentication using a username and password, as well as OAuth authentication with providers like Google, Facebook, and Twitter.
Setting Up the Project
1. Initialize a New Node.js Project
mkdir passport-auth
cd passport-auth
npm init -y
2. Install Required Dependencies
npm install express express-session passport passport-local bcryptjs ejs
- express: Web framework for Node.js.
- express-session: Middleware for managing sessions.
- passport: Authentication middleware.
- passport-local: Strategy for local authentication.
- bcryptjs: Library for hashing passwords.
- ejs: Template engine for rendering views.
Project Structure
passport-auth/
├── app.js
├── package.json
├── views/
│ ├── index.ejs
│ ├── login.ejs
│ └── register.ejs
Implementing Authentication
1. Create the Main Application File (app.js
)
// app.js
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const bcrypt = require('bcryptjs');
const LocalStrategy = require('passport-local').Strategy;
const path = require('path');
const app = express();
// In-memory user store (Replace with a database in production)
const users = [];
// Middleware
app.set('view engine', 'ejs');
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));
// Session management
app.use(
session({
secret: 'your_secret_key',
resave: false,
saveUninitialized: false,
})
);
// Initialize Passport.js
app.use(passport.initialize());
app.use(passport.session());
// Passport Local Strategy
passport.use(
new LocalStrategy((username, password, done) => {
// Find user
const user = users.find((u) => u.username === username);
if (!user) return done(null, false, { message: 'Incorrect username.' });
// Validate password
bcrypt.compare(password, user.password, (err, res) => {
if (res) return done(null, user);
else return done(null, false, { message: 'Incorrect password.' });
});
})
);
// Serialize and deserialize user
passport.serializeUser((user, done) => done(null, user.username));
passport.deserializeUser((username, done) => {
const user = users.find((u) => u.username === username);
done(null, user);
});
// Routes
app.get('/', (req, res) => {
res.render('index', { user: req.user });
});
app.get('/login', (req, res) => res.render('login'));
app.post(
'/login',
passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login',
})
);
app.get('/register', (req, res) => res.render('register'));
app.post('/register', (req, res) => {
const { username, password } = req.body;
// Hash password
bcrypt.hash(password, 10, (err, hashedPassword) => {
if (err) return res.redirect('/register');
// Store user
users.push({ username, password: hashedPassword });
res.redirect('/login');
});
});
app.get('/logout', (req, res, next) => {
req.logout((err) => {
if (err) return next(err);
res.redirect('/');
});
});
// Protected route
app.get('/profile', ensureAuthenticated, (req, res) => {
res.send(`Hello, ${req.user.username}! This is your profile.`);
});
// Authentication check middleware
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) return next();
res.redirect('/login');
}
// Start server
app.listen(3000, () => console.log('Server started on port 3000'));
2. Create Views
- views/index.ejs
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>Welcome<% if (user) { %>, <%= user.username %><% } %>!</h1>
<% if (user) { %>
<a href="/logout">Logout</a> | <a href="/profile">Profile</a>
<% } else { %>
<a href="/login">Login</a> | <a href="/register">Register</a>
<% } %>
</body>
</html>
- views/login.ejs
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<form action="/login" method="post">
<div>
<label>Username:</label>
<input type="text" name="username" required />
</div>
<div>
<label>Password:</label>
<input type="password" name="password" required />
</div>
<button type="submit">Login</button>
</form>
</body>
</html>
- views/register.ejs
<!DOCTYPE html>
<html>
<head>
<title>Register</title>
</head>
<body>
<h1>Register</h1>
<form action="/register" method="post">
<div>
<label>Username:</label>
<input type="text" name="username" required />
</div>
<div>
<label>Password:</label>
<input type="password" name="password" required />
</div>
<button type="submit">Register</button>
</form>
</body>
</html>
Understanding the Code
1. User Storage
- For simplicity, we’re using an in-memory array
users
. In a production environment, you should use a database like MongoDB.
2. Middleware Setup
- express.urlencoded: Parses URL-encoded bodies (form data).
- express.static: Serves static files from the
public
directory.
3. Session Management
- express-session: Configures session handling with a secret key.
4. Passport Configuration
- Initialize Passport:
app.use(passport.initialize())
. - Session Support:
app.use(passport.session())
. - Local Strategy: Defines how to authenticate users using a username and password.
- Serialize/Deserialize User: Determines how user information is stored in the session.
5. Routes
- Home (
/
): Displays a welcome message and navigation links based on authentication status. - Login (
/login
): Renders the login form and handles login submissions. - Register (
/register
): Renders the registration form and handles new user registration. - Logout (
/logout
): Logs the user out and redirects to the home page. - Profile (
/profile
): A protected route that only authenticated users can access.
6. Authentication Middleware
- ensureAuthenticated: A custom middleware function that checks if a user is authenticated before allowing access to certain routes.
Testing the Application
- Start the Server
node app.js
- Access the Application
- Navigate to
http://localhost:3000
in your web browser. - Register a new user.
- Log in with the newly created account.
- Access the protected profile page.
Next Steps
- Persistent Storage: Integrate a database (e.g., MongoDB) to store users persistently.
- Error Handling: Improve feedback to users on authentication failures.
- Security Enhancements:
- Use environment variables for sensitive information.
- Implement HTTPS.
- Set secure session cookies.
Conclusion
By following this guide, you’ve set up a basic Node.js application with user authentication using Passport.js. This foundational setup can be expanded to include more features and security measures as needed.