Free to list, always.No paid rankings. Every recommendation explains its trade-offs.
OpenSourceChoice
Backend · Advanced

Authentication with Node.js

Implement password hashing and JWT authentication in Node.js, then evaluate sessions, secret handling, authorization, and production risks.

3 hoursUpdated Jul 21, 2026389 views

Implement password hashing and JWT-based auth for an Express API—without turning the tutorial into a false sense of production readiness.

What you will learn

  • Hash passwords with bcrypt
  • Issue and verify JWTs
  • Protect routes with middleware
  • Know which security controls still belong in production hardening

Prerequisites

  • Working Express basics
  • Node.js 20+
  • Comfortable reading async route handlers

Install

npm install express bcryptjs jsonwebtoken dotenv zod

Set JWT_SECRET in .env (long random string). Never commit it.

Register and login sketch

import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";

const users = new Map(); // replace with a real database
const secret = process.env.JWT_SECRET;
if (!secret) throw new Error("JWT_SECRET missing");

export async function register(email, password) {
  if (users.has(email)) throw Object.assign(new Error("exists"), { status: 409 });
  const passwordHash = await bcrypt.hash(password, 12);
  const user = { id: crypto.randomUUID(), email, passwordHash };
  users.set(email, user);
  return { id: user.id, email: user.email };
}

export async function login(email, password) {
  const user = users.get(email);
  if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
    throw Object.assign(new Error("invalid_credentials"), { status: 401 });
  }
  const token = jwt.sign({ sub: user.id, email: user.email }, secret, { expiresIn: "1h" });
  return { token };
}

Wire POST /auth/register and POST /auth/login to these functions. Return generic errors on login failure so you do not reveal whether an email exists.

Auth middleware

export function requireAuth(req, res, next) {
  const header = req.headers.authorization || "";
  const token = header.startsWith("Bearer ") ? header.slice(7) : "";
  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch {
    res.status(401).json({ error: "unauthorized" });
  }
}

// example protected route
// app.get("/me", requireAuth, (req, res) => res.json({ user: req.user }));

Clients send Authorization: Bearer <token>.

Production warnings

  • Use HTTPS everywhere
  • Prefer short-lived access tokens + refresh strategy
  • Store secrets in a vault/env manager, never in git
  • Add rate limiting and lockouts for auth endpoints
  • Consider established libraries (Passport, Better Auth, Lucia, Auth.js) instead of a home-grown system for real products

Practice checklist

  • Register a user, login, and call a protected route with the token
  • Prove an invalid token returns 401
  • Rotate JWT_SECRET locally and observe old tokens fail verification
  • Read the OWASP auth cheat sheet section on session vs token threats

Sources and further reading

Who this guide is for

  • Backend developers learning token authentication
  • Teams prototyping an API before selecting an identity provider
  • Reviewers assessing whether a JWT design is production-ready

Definition of done

A threat-modelled authentication prototype with safe password handling, short-lived access tokens, explicit authorization checks, revocation strategy, audit events, and tests for abuse cases.

Finishing the commands is not the same as finishing the guide. Keep the result only when another person can reproduce it, inspect the evidence, and understand the remaining risks.

Professional workflow

Phase 1 — Model threats and trust boundaries

Goal: Decide what the system protects before choosing token settings.

  • List account, credential, token, and administrative assets.
  • Identify browser, mobile, API, and third-party clients.
  • Define likely abuse: stuffing, replay, theft, enumeration, and privilege escalation.

Verification: Each high-impact threat has a preventive control and an observable signal.

Phase 2 — Implement identity safely

Goal: Keep credential verification and token issuance narrow.

  • Validate normalized identifiers and strong password rules.
  • Hash passwords with an adaptive algorithm and reviewed work factor.
  • Issue minimal claims with issuer, audience, subject, and expiry.

Verification: Tests reject invalid credentials without revealing whether an account exists.

Phase 3 — Authorize and recover

Goal: Treat authentication as only the first gate.

  • Check permissions at every protected resource boundary.
  • Design logout, key rotation, password reset, and compromised-token response.
  • Record security events without logging secrets or tokens.

Verification: A revoked or downgraded user cannot retain old privileges beyond the documented window.

Review questions before you continue

  • Why is JWT preferable to an opaque session for this client?
  • Where and how are tokens stored?
  • How are keys rotated without an outage?
  • What limits brute force and credential stuffing?

If an answer is unknown, record it as an explicit follow-up instead of hiding the uncertainty behind a successful demo.

Troubleshooting decision guide

  • Valid-looking tokens fail: verify issuer, audience, algorithm, clock skew, and key selection.
  • Logout has no immediate effect: document the access-token window and add revocation where required.
  • Authorization leaks across users: query resources using both resource ID and authenticated owner scope.
  • Password hashing overloads the service: benchmark the work factor and bound concurrent login attempts.

Change one variable at a time, preserve the first useful error, and write down the command or condition that fixed it. That turns a private debugging session into team knowledge.

Production-readiness checklist

  • Prefer a mature identity provider when authentication is not your core competency.
  • Use TLS everywhere and protect token storage against the client threat model.
  • Rate-limit and monitor registration, login, reset, and verification endpoints.
  • Rotate signing keys and secrets through managed configuration.
  • Schedule an independent security review before handling real accounts.

These checks are intentionally stricter than a beginner demo. Use them to decide whether to continue learning, run a controlled pilot, or request specialist review.

Your next 30 minutes

  • Write the threat model and token lifetime rationale.
  • Test expired, malformed, wrong-audience, and privilege-escalation cases.
  • Create a key-rotation and incident-response runbook.
  • Do not call the prototype production-ready until every checklist item is owned.

CTA: Pick the first unchecked step, complete it now, and save the evidence in your project README or decision log. Then open the official documentation linked below before adopting the result in production.

Similar guides

Keep learning in the same lane.

Matched by technology, category, tags, and connected software—not ranked sponsorship.

IntermediateCreating a RESTful API with Node.js and Express

Build and test a REST API with Node.js and Express using structured routes, validation, environment configuration, and reliable error handling.

Node.js · 3 hours
Browse the full library →
Turn reading into evidence

Make this guide useful today.

Complete one verification step, save the result in your project notes, and decide whether you are ready for the next phase. A reproducible result is more valuable than a finished page.

  • Run the smallest working example.
  • Test one failure or edge case.
  • Record the command, result, and remaining risk.