Admin Authentication

Website Rebuild with Express

Chapter 9 · Admin Authentication

No framework-provided auth mechanism at all — Chapter 1's own finding. This chapter builds real auth by hand, reusing what's already proven to work twice in this series.

Session Handling: express-session

// server.js const session = require('express-session'); app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { secure: true, httpOnly: true }, }));

Even Astro at least had @auth/astro's own session handling built into its integration. Express has none — express-session is a separate package, chosen and wired by hand, for a job every other framework in the series provided some form of by default.

The Login Route

const bcrypt = require('bcryptjs'); app.post('/admin/login', async (req, res) => { const { email, password } = req.body; const [rows] = await pool.query('SELECT * FROM admin_users WHERE email = ?', [email]); if (rows.length === 0) { return res.status(401).json({ error: 'Invalid credentials' }); } const valid = await bcrypt.compare(password, rows[0].password_hash); if (!valid) { return res.status(401).json({ error: 'Invalid credentials' }); } req.session.userId = rows[0].id; res.json({ status: 'ok' }); });
The third confirmation — the identical library, a third time
bcryptjs is the same npm package the Next.js rebuild's own Chapter 9 first used, and the Astro rebuild's own Chapter 9 confirmed again. This is the third time in this series the exact same library has verified the exact same legacy $2y$-tagged hash — not even a cross-ecosystem question here either, since it's still the identical JavaScript package doing the identical job.

Closing Chapter 8's Gap — a Real, General-Purpose Mechanism

// middleware/requireAuth.js function requireAuth(req, res, next) { if (!req.session.userId) { return res.status(401).json({ error: 'Not authenticated' }); } next(); } module.exports = requireAuth;
// routes/admin.js — updated from Chapter 8 const requireAuth = require('../middleware/requireAuth'); app.post('/api/pages/:id/title', requireAuth, async (req, res) => { // ...unchanged from Chapter 8 });
Minimalism doesn't mean no structure — a genuine positive finding
Express provides no specific auth middleware, but it does provide a real, first-class, general-purpose extensibility mechanism — the (req, res, next) middleware signature — used here for exactly the same purpose Rails' before_action or Laravel's route middleware serve. requireAuth isn't a workaround; it's Express's own standard pattern for this job, just without a specific, pre-built version of it. Minimalism here produces something genuinely clean, not a gap.

Six Frameworks' Own Auth Stories, Closed Out

Next.jsDjangoLaravelRailsAstroExpress
MechanismAuth.jsBuilt-in auth appBuilt-in Auth facadehas_secure_password@auth/astro (same Auth.js)Hand-written, via bcryptjs + express-session
Session handlingBuilt into Auth.jsBuilt inBuilt inBuilt inBuilt into @auth/astroSeparate package (express-session)
Bcrypt match?Yes, via bcryptjsNo — needed reconfigurationYes, zero configYes, with a real tag nuanceYes — same bcryptjs packageYes — same bcryptjs package, a third time

Hands-On Exercises

Exercise 1

Set up express-session and the login route with bcryptjs, and confirm a correct login sets req.session.userId while an incorrect password returns 401.

📄 View solution
Exercise 2

Build the requireAuth middleware and apply it to Chapter 8's own endpoint, and confirm an unauthenticated request is now rejected with 401.

📄 View solution
Exercise 3

Confirm bcryptjs correctly verifies the legacy $2y$-tagged hash with no configuration, and name the two earlier chapters in this series where the identical package already did the same job.

📄 View solution

Chapter 9 Quick Reference

  • express-session — a separate package; Express provides no session handling of its own at all
  • bcryptjs — the same npm package as Next.js and Astro, verifying the legacy hash a third time
  • requireAuth — a hand-written middleware using Express's own real, general-purpose (req, res, next) mechanism
  • A genuine positive finding — minimalism here produces a clean, standard pattern, not a gap
  • Next chapter: Admin CRUD Interface