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 authentication — Access Token + Refresh Token.
| 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 |
Let’s see how the full system works.
session table).✅ Example: