A JSON Web Token (JWT) is a compact, signed token that encodes claims about an authenticated subject so a server can verify identity without querying a session store on every request.
In simpler words
A JWT is a signed ID card: once someone logs in, the server hands them a token that proves who they are without looking anything up again for every request.
In a Nest API, JWTs are typically issued on login and read from an httpOnly cookie on every request. Bearer-header JWTs are the same token format used by mobile apps and API tooling — a useful mental model even though the browser flow here relies on cookies.
Trace a JWT from login to a request that reuses it
Explain why the payload stays small (sub, email, role)
Compare cookie transport with the bearer-header mental model
How a Nest API issues and reads a JWT
Definition
AuthService.login verifies credentials, asks JwtService.signAsync for a signed token carrying sub, email, and role, and stores that token in an httpOnly access_token cookie; JwtStrategy later extracts and verifies the same token on every request.
In simpler words
Login proves identity once; the cookie carries a compact, tamper-evident version of that decision on every following request.
The signature (not encryption) is what makes a JWT trustworthy: the server can detect any edit to the payload, but the payload itself is base64, not secret.
Because verification only needs the signing secret, Nest can authenticate a request without a database round trip for the token itself — it still loads the user record to get the current role and confirm the account still exists.
Only sub, email, and role travel in the token — enough to authorize, not a full user profile.
Cookie session here, bearer header as the wider mental model
Definition
Cookie-based JWT delivery lets the browser attach the token automatically on same-site credentialed requests, while bearer-header delivery requires the client to read the token and set an Authorization: Bearer header on every request.
In simpler words
The browser never touches the token string here; other JWT clients — mobile apps, curl, Swagger — typically carry the same token by hand in a header.
A JwtStrategy can accept both: it tries the access_token cookie first, then falls back to an Authorization Bearer header — that fallback exists mainly for Swagger and tooling, not for the web app.
A cookie flow needs credentials: true on both the client and Nest CORS; a bearer flow needs the client to store and attach the token itself, which is exactly what an httpOnly cookie is designed to avoid.
Cookie wins when present; bearer is the fallback used by Swagger’s Authorize flow and other non-browser clients.
Signed ≠ encrypted; expiry and CSRF notes
Definition
A JWT signature proves integrity and authenticity of the claims under a shared secret (or private key); it does not hide the payload. Cookie-based APIs must also consider cross-site request risks.
In simpler words
Anyone can decode the payload; only the server with the secret can forge a valid signature. Treat claims as public-ish, not secret.
Do not put passwords, tokens, or PII beyond what the API truly needs in the JWT. Prefer ids and role.
Expiry (exp) bounds damage if a token leaks. This course uses a multi-day cookie maxAge — production teams often shorten access tokens and add refresh flows.
httpOnly + Secure (in HTTPS) + SameSite=Lax/Strict reduces XSS token theft and some CSRF classes. State-changing cookie APIs still need CSRF strategy when cross-site posts are possible.
@Public() routes skip JwtAuthGuard — only mark truly public endpoints (login, health, register).
Mistake: localStorage JWT
// Wrong — any XSS can steal the token
localStorage.setItem('token', jwt);
// Right — httpOnly cookie set by Nest
res.cookie(AUTH_COOKIE_NAME, token, { httpOnly: true, sameSite: 'lax', secure });
If JavaScript can read the credential, an XSS bug becomes a session theft bug.
Mistake: huge JWT payload
// Wrong — entire user profile + permissions tree in the token
signAsync({ ...userEntity, permissions: fullTree })
// Right — minimal claims; load fresh user/role server-side when needed
signAsync({ sub: user.id, email: user.email, role: user.role })
Large tokens bloat every request and go stale when roles change; here the user record is reloaded in JwtStrategy.
Keep in mind
Keep JWT payloads small and non-sensitive — sub, email, role, nothing else.
Treat the cookie as the transport used here; the bearer header is the same token format used elsewhere.
Never store a JWT in localStorage when an httpOnly cookie is available.
Signed means tamper-evident, not secret — do not put secrets in claims.
Test
Check your understanding
At least 10 questions — mix of concept, syntax, practical, and logic. Score ≥80% (enforced by the API) to save progress.