ExpressJS - Authentication
Express.js - Modern User Authentication
Section titled “Express.js - Modern User Authentication”Authentication is the process of verifying a user’s identity. In web applications, this typically involves a username/email and a password. This tutorial demonstrates a modern, secure approach to building an authentication system using sessions.
Security Warning: This example uses an in-memory array to store users for simplicity. In a real-world application, you must use a persistent database (like PostgreSQL or MongoDB) to store user data.
Step 1: Project Setup & Dependencies
Section titled “Step 1: Project Setup & Dependencies”Start with a new Express project (see the Environment Setup chapter) and install the necessary packages for handling sessions and hashing passwords:
# For session managementnpm install express-session
# For securely hashing passwordsnpm install bcrypt
# A template engine for rendering viewsnpm install pugStep 2: Structuring the Application
Section titled “Step 2: Structuring the Application”A good structure is key. Create the following files and directories:
.├── src/│ ├── views/ # Pug templates│ │ ├── login.pug│ │ ├── signup.pug│ │ └── protected.pug│ └── index.js # Main application file├── .env # Environment variables├── package.json└── ...Create a .env file in your root directory to store your session secret. This should never be committed to git.
SESSION_SECRET='a-very-strong-and-long-random-secret-key'Step 3: Main Application Logic (src/index.js)
Section titled “Step 3: Main Application Logic (src/index.js)”This file will contain all our logic. In a larger app, you would split this into routes, controllers, and services.
import express from 'express';import session from 'express-session';import bcrypt from 'bcrypt';import 'dotenv/config'; // Loads .env file
const app = express();const PORT = 3000;
// In-memory user store (FOR DEMO PURPOSES ONLY!)const users = [];
// --- Middleware Setup ---app.set('view engine', 'pug');app.set('views', './src/views');
app.use(express.json());app.use(express.urlencoded({ extended: true }));app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { secure: false } // In production, set to true with HTTPS}));
// --- Custom Middleware for Protecting Routes ---const checkAuth = (req, res, next) => { if (req.session.user) { next(); // User is authenticated, proceed } else { res.redirect('/login'); // Not authenticated, redirect to login }};
// --- Routes ---app.get('/', (req, res) => { res.redirect('/login');});
app.get('/signup', (req, res) => { res.render('signup');});
app.post('/signup', async (req, res) => { const { username, password } = req.body; if (!username || !password) { return res.status(400).render('signup', { message: 'Username and password are required.' }); } if (users.find(user => user.username === username)) { return res.status(400).render('signup', { message: 'Username already exists.' }); }
try { const hashedPassword = await bcrypt.hash(password, 10); // Hash the password const newUser = { username, password: hashedPassword }; users.push(newUser); console.log('New user registered:', newUser); res.redirect('/login'); } catch (error) { console.error(error); res.status(500).send('Server error'); }});
app.get('/login', (req, res) => { res.render('login');});
app.post('/login', async (req, res) => { const { username, password } = req.body; const user = users.find(u => u.username === username); if (!user) { return res.status(401).render('login', { message: 'Invalid credentials.' }); }
try { const isMatch = await bcrypt.compare(password, user.password); if (isMatch) { req.session.user = { username: user.username }; // Store user in session res.redirect('/protected'); } else { res.status(401).render('login', { message: 'Invalid credentials.' }); } } catch (error) { console.error(error); res.status(500).send('Server error'); }});
app.get('/protected', checkAuth, (req, res) => { res.render('protected', { username: req.session.user.username });});
app.post('/logout', (req, res) => { req.session.destroy(err => { if (err) { return res.status(500).send('Could not log out.'); } res.redirect('/login'); });});
app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));Step 4: Creating the Views (Pug Templates)
Section titled “Step 4: Creating the Views (Pug Templates)”Create the following three .pug files in your src/views directory.
signup.pug
Section titled “signup.pug”html head title Signup body h1 Create an Account if message p(style={color: 'red'}) #{message} form(action="/signup" method="POST") div label(for="username") Username: input(name="username" type="text" required) div label(for="password") Password: input(name="password" type="password" required) button(type="submit") Sign Up p Already have an account? <a href="/login">Login</a>login.pug
Section titled “login.pug”html head title Login body h1 Login if message p(style={color: 'red'}) #{message} form(action="/login" method="POST") div label(for="username") Username: input(name="username" type="text" required) div label(for="password") Password: input(name="password" type="password" required) button(type="submit") Log In p Don't have an account? <a href="/signup">Sign up</a>protected.pug
Section titled “protected.pug”html head title Protected Area body h1 Welcome, #{username}! p This is a protected page. You can only see it if you are logged in. form(action="/logout" method="POST") button(type="submit") Log OutRunning and Testing the Application
Section titled “Running and Testing the Application”Run npm run dev and navigate to http://localhost:3000. You’ll be redirected to the login page. Try to access /protected directly; you will be sent back to login. Now, sign up for a new account, then log in with those credentials to access the protected page.
Modern Alternative: JWT (JSON Web Tokens)
Section titled “Modern Alternative: JWT (JSON Web Tokens)”While sessions are great, another popular method for authentication is using JWTs. This is a stateless approach, meaning the server doesn’t need to store session data. The flow is:
- User logs in.
- Server validates credentials and returns a signed JWT to the client.
- Client stores the JWT (e.g., in an
HttpOnlycookie) and sends it in theAuthorizationheader of subsequent requests. - Server verifies the JWT on each request to identify the user. This method is especially useful for Single Page Applications (SPAs) and mobile apps.