AV
HomeAboutProjectBlog

© 2026 Ave syah Shina. All rights reserved.

  1. Home
  2. Blog
  3. 09 — Authentication — JWT and Passport.js

09 — Authentication — JWT and Passport.js

August 13, 20266 min read
Download as Markdown

"Just log them in" was my auth mental model, and it fused two different jobs. The model that finally stuck splits into two pieces: JWT is a signed, stateless token for proving identity across requests, and Passport.js is the pluggable middleware that handles the messy parts of actually verifying that identity. They are not alternatives — JWT is the what-you-carry, Passport is the how-you-checked-it [1].

The framing that landed for me is the stateless-then-stateful split: a token is a self-contained credential, and the verification step is where the real auth logic lives.

The problem: HTTP is stateless

Every HTTP request stands alone — the server remembers nothing between requests by default. So if a user logs in on request one, the server needs a way to know, on request two, that this is still them. Two broad strategies exist:

  • Server-side sessions — the server stores a record keyed by a random id, sends the id in a cookie, and looks it up on each request. The server keeps state; revoking is trivial.
  • Tokens — the server hands the client a signed credential (the token) that the client includes on every request. The server verifies the signature and reads the claims; no lookup, no stored state.

JWT is the dominant token format. The trade-off versus sessions is real: tokens are self-contained (good for distributed systems), but they cannot be cleanly revoked before they expire (bad for "log out everywhere"), because the server does not track them. Picking the model is a statelessness-versus-revocation question.

JWT: anatomy of a token

A JSON Web Token is three base64-encoded parts joined by dots — header.payload.signature [1][2]. The header names the algorithm. The payload is a set of claims: statements about the subject (user id, roles, expiry). The signature is the header and payload signed with a secret or private key, so any tampering with the first two parts invalidates the third.

HEADER { "alg": "HS256", "typ": "JWT" } base64-encoded . PAYLOAD (claims) { "sub": "42", "role": "admin", "exp": 1735689600 } . SIGNATURE HMAC-SHA256( base64(header) + "." + base64(payload), secret) verify = re-sign header.payload with the secret and compare

Two properties follow from that structure. The token is not encrypted — anyone with the token can base64-decode the payload and read it. The signature only guarantees integrity (it was not tampered with) and origin (it was signed by whoever holds the secret), not confidentiality. Sensitive data does not go in the payload. And the token is verifiable without a lookup — the server recomputes the signature from header + payload using its secret and checks that it matches. No database hit, which is the whole appeal for distributed systems [1].

Issuing and verifying with jsonwebtoken

The jsonwebtoken package is the standard library for this [3].

import jwt from 'jsonwebtoken';

const SECRET = process.env.JWT_SECRET;

// issue — after verifying credentials
const token = jwt.sign({ sub: '42', role: 'admin' }, SECRET, { expiresIn: '1h' });

// verify — on each authenticated request
try {
const payload = jwt.verify(token, SECRET);
// payload.sub === '42', payload.exp checked automatically
} catch (err) {
// invalid signature or expired
}

The pattern: on login, issue a short-lived token; on each request, verify it in middleware and attach the decoded claims to the request. The exp claim is what makes tokens self-expire — jwt.verify rejects an expired token automatically. The discipline I keep: short expiry (minutes to an hour), keep the secret in process.env (never in code), and never put anything in the payload I would not want a curious user to read.

Passport.js: pluggable verification

JWT handles the carry a signed credential half. The other half — how do I verify the user actually is who they claim at login time — is messier, because it involves checking passwords, talking to OAuth providers, validating one-time codes, and so on. Passport.js is authentication middleware for Express that abstracts this into strategies [4][5].

A strategy is a self-contained module that knows one way to authenticate: passport-local for username/password, passport-google-oauth20 for Google OAuth, passport-jwt for extracting and verifying a JWT on protected routes. Each strategy implements a verification function; Passport calls it and hands me the result.

import passport from 'passport';
import { Strategy as LocalStrategy } from 'passport-local';

passport.use(new LocalStrategy(
async (username, password, done) => {
const user = await User.findOne({ username });
if (!user || !(await user.checkPassword(password))) {
return done(null, false); // credentials wrong
}
return done(null, user); // success
}
));

// in the route
app.post('/login', passport.authenticate('local'), (req, res) => {
const token = jwt.sign({ sub: req.user.id }, SECRET, { expiresIn: '1h' });
res.json({ token });
});

The value of Passport is not any single strategy — it is the uniform interface. "Authenticate with Google" and "authenticate with a password" plug into the same passport.authenticate(...) call, and swapping one for the other is a configuration change rather than a rewrite. For a project with multiple login methods, that modularity pays for itself.

How I use this

The model I keep is the stateless-then-stateful split, and it drives a few habits. I default to JWT for stateless APIs (and short expiry, since revocation is hard); I reach for server-side sessions when "log out everywhere" or instant revocation is a real requirement. I treat the JWT payload as readable by the client — no secrets in it — and rely on the signature for integrity. For the verification half, I use Passport when there is more than one auth method, because the strategy abstraction makes adding a provider a small change. And the secret always lives in process.env, rotated periodically, because a leaked secret means every token ever issued with it can be forged. The split — token is the carrier, Passport is the checker — is what keeps auth code organized.

References

[1] "What is JWT?," Akana Blog. [Online]. Available: https://www.akana.com/blog/what-is-jwt

[2] "jsonwebtoken," npm. [Online]. Available: https://www.npmjs.com/package/jsonwebtoken

[3] "JWT Implementation," YouTube. [Online]. Available: https://www.youtube.com/watch?v=mbsmsi7l3r4

[4] "Passport.js," passportjs.org. [Online]. Available: https://www.passportjs.org/

[5] "Passport.js Documentation," passportjs.org. [Online]. Available: https://www.passportjs.org/docs/

Knowledge check · Question 1 of 5

A JWT's signature provides…

Comments

Leave a Comment

You must be signed in to comment

0 Comments

No comments yet. Be the first to comment!