Module P-2·25 min read

JWT structure, signing and verification, refresh token rotation, bcrypt cost factors, Express auth middleware, session vs stateless — and OAuth 2.0 social login.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

Module P-2 — Authentication and Authorization

What this module covers: Authentication answers "who are you?" Authorization answers "what are you allowed to do?" These are two distinct concerns that most tutorials conflate. This module covers the complete production authentication stack: bcrypt for password hashing, JWT for stateless tokens, refresh token rotation for long-lived sessions, Express middleware for protecting routes, and role-based access control. By the end you will have a working auth system you can drop into any Node.js API.


Authentication vs Authorization

Before writing any code, get the terminology straight — confusing these two causes real security bugs:

  • Authentication — verifying identity. "Is this really Jatin?" Handled by login, tokens, sessions.
  • Authorization — verifying permission. "Is Jatin allowed to delete this post?" Handled by role checks, ownership checks, policies.

A user can be fully authenticated (we know who they are) and still be unauthorized (they don't have permission for this specific action). Both checks are needed, and they run in that order.


Passwords: Never Store Plaintext

If your database is ever breached, plaintext passwords give attackers instant access to every account — and to every other site where users reused that password. Always hash passwords with a slow, purpose-built algorithm.

bcrypt is the standard. It uses a configurable "cost factor" (work factor) that controls how long hashing takes. The higher the cost, the more computation an attacker needs to brute-force the hash.

bash
javascript

Cost factor guidelines:

EnvironmentRecommended costApprox. time per hash
Development10~65ms
Production12~250ms
High-security14~1 second

At cost 12, a user waits ~250ms on login — imperceptible. An attacker trying to brute-force a leaked database hash faces 250ms per attempt. At scale, that's the difference between cracking a password in hours vs years.

Timing-safe comparison: bcrypt.compare is timing-safe — it takes the same amount of time regardless of whether the password is correct or not. This prevents timing attacks that infer the correct password by measuring response time differences.


JSON Web Tokens (JWT)

After a user authenticates, you need a way to identify them on subsequent requests without asking for their password again. JWT is the most common stateless solution.

bash

JWT Structure

A JWT is a Base64URL-encoded string with three parts separated by dots: header.payload.signature (Base64URL, not standard Base64, because standard Base64's +, /, and = characters aren't safe to put in a URL or header value unescaped)

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MiIsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNzE2MjkwMDAwLCJleHAiOjE3MTYyOTM2MDB9.abc123...
  • Header — algorithm used (HS256, RS256)
  • Payload — claims: sub (subject/userId), role, iat (issued at), exp (expiry)
  • Signature — HMAC of header+payload using your secret key

The payload is Base64-encoded, not encrypted — anyone can decode and read it. Never put sensitive data (passwords, PII) in the payload. The signature guarantees it has not been tampered with.

Signing and verifying tokens

javascript

JWT Algorithm-Confusion Attacks

jwt.verify(token, ACCESS_SECRET) above works, but omitting the algorithms option is itself a known vulnerability class. jsonwebtoken will accept whatever algorithm the token's own header claims. If your codebase ever also handles RS256 tokens (signed with an RSA private key, verified with the corresponding public key) anywhere, an attacker can craft a token signed with HMAC using that public key as the secret — public keys are, by definition, public — and it will pass verification if the code trusts the header's algorithm. This is not theoretical; it is a real CVE-class bug that has hit multiple JWT libraries and frameworks over the years.

The fix costs one line — pin the algorithm explicitly instead of trusting the token to declare it:

javascript

Apply this to every jwt.verify call in the codebase, not just this one — it tells the library "only ever trust HS256 signatures," full stop, regardless of what the token header says.

bash

Generate secure secrets:

bash

Why Two Tokens?

Analogy: think of the access token as a hotel keycard that demagnetizes itself every 15 minutes, while the refresh token is the entry in the front desk's register that decides whether housekeeping will cut you a new card at all.

A single long-lived token is a security liability — if stolen, the attacker has access for days or weeks. The two-token pattern mitigates this:

  • Access token — short-lived (15 min). Sent with every API request. Verified entirely from the signature — no database lookup needed. If stolen, expires quickly.
  • Refresh token — long-lived (7 days). Stored securely. Used only to get a new access token when the old one expires. Can be invalidated by deleting it from the database.
text

The Complete Auth Flow

User registration

javascript

User login

javascript

Token refresh

javascript

Refresh token rotation: every time a refresh token is used, it is deleted and a new one is issued. If a stolen refresh token is used, the legitimate user's next refresh will fail (their token was also invalidated). This is how you detect token theft.

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.