Why "Just JWT" Isn’t Enough

For small projects, developers often issue a single JWT after login:

const token = jwt.sign({ userId }, process.env.JWT_SECRET!, { expiresIn: '7d' });

✅ Simple, but this approach fails hard in production.

Problem Why It’s Bad
🔒 Token can’t be revoked You can’t “log out” users until expiry.
⏳ Long expiry = insecure If leaked, attacker has full access for days.
😤 Short expiry = bad UX Users must re-login too often.
🧑‍💻 No device/session control You can’t track sessions per device or revoke one.
🔁 Can’t rotate tokens No mechanism to detect compromised tokens.

👉 So enterprise systems use two-token authenticationAccess Token + Refresh Token.


The Two-Token Model

Token Lifetime Stored Where Purpose
Access Token (JWT) 15 minutes Memory / HTTP-only Cookie Authenticates API requests
Refresh Token (JWT referencing random ID) 30 days HTTP-only Cookie / Encrypted Storage Refreshes access token

Authentication Flow

Let’s see how the full system works.


Step 1: Login

  1. User submits credentials.
  2. Backend verifies them.
  3. Backend generates:
  4. Store refresh token identifier in DB (session table).
  5. Return tokens to client (in cookies or JSON).

✅ Example: