The end-to-end journey
[Front] login(user, password)
│ POST /login { user, password }
▼
[Back] validates credentials ──> issues signed token (with exp)
│ 200 { token }
▼
[Front] stores the token and attaches it on every request:
│ GET /profile Authorization: Bearer <token>
▼
[Back] verifies the token ──> knows who you are ──> responds
Step by step
- Login: the user sends
userandpassword. The backend checks the password (against the stored hash, never against the plaintext password). - Issuance: if correct, the backend issues a credential: a signed
token (JWT) or a session id. It includes an expiry (
exp). - Stored in the front: the frontend stores the credential (cookie or
localStorage). - Attach: on every later request the front sends the credential. With
JWT, in the
Authorization: Bearer <token>header. - Verification: the backend reads the header, verifies the signature and the
expiry, and loads the user (e.g. into
req.user). - Protected routes: a middleware blocks the way with 401 if there's no valid credential, before reaching the handler.
Expiration and refresh tokens
An access token must last little (minutes): if it's stolen, the damage expires soon. But asking the user to log in again every 15 minutes is horrible. Solution: two tokens.
- Access token: short-lived, attached on every request.
- Refresh token: long-lived, stored more carefully and only used to request a new access token when the previous one expires, without re-login.
This way you combine security (small theft window) and convenience (long sessions without bothering the user).
Examples
Read 'Bearer <token>' from the Authorization header
const headers = { Authorization: "Bearer abc.def.ghi" };
const header = headers.Authorization || "";
const [scheme, token] = header.split(" ");
console.log(scheme, token); // Bearer abc.def.ghi