What we're building A minimal but production-shaped Express API with: a login endpoint that issues a JWT, middleware that protects routes by requiring a valid token, and a refresh endpoint. We'll use the popular `jsonwebtoken` package and `bcrypt` for password hashing.
Step 1: Install dependencies Install `express`, `jsonwebtoken`, and `bcrypt`. In a real project you'd also add a database client; this walkthrough uses an in-memory user store for clarity.
Step 2: Generate a strong signing secret Never hardcode a guessable string. Generate one with `crypto.randomBytes(64).toString('base64')` and store it in an environment variable, e.g. `JWT_SECRET`. See our dedicated secret generation guide for the full reasoning, and use this site's generator to produce one instantly.
Step 3: Hash passwords at signup Store only a bcrypt hash of the user's password, never the plaintext, using something like `bcrypt.hash(password, 12)`. This is unrelated to JWTs directly but is a prerequisite for any login endpoint worth building.
Step 4: The login endpoint On `POST /login`: - Look up the user by email. - Compare the submitted password against the stored hash with `bcrypt.compare`. - If it matches, sign a JWT with `jwt.sign({ sub: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: "15m", algorithm: "HS256" })`. - Also issue a refresh token: a random opaque string, stored (hashed) in your database alongside the user ID and an expiry, then set as an HttpOnly, Secure, SameSite cookie. - Return the access token in the JSON response body for the client to attach to future requests.
Step 5: Authentication middleware Write a small middleware that: - Reads the `Authorization: Bearer <token>` header. - Returns **401** immediately if it's missing or malformed. - Calls `jwt.verify(token, process.env.JWT_SECRET, { algorithms: ["HS256"], clockTolerance: 15 })` inside a try/catch. - On failure (expired, bad signature, wrong algorithm), returns **401**. - On success, attaches the decoded payload to `req.user` and calls `next()`.
Note the explicit algorithms array — this is what prevents algorithm confusion attacks discussed elsewhere on this blog.