The web application security checklist we run before every launch
Not a compliance document. This is the list we actually work through before a client site handles its first real user.
Server-side sessions with an HttpOnly cookie are the safest default for typical web applications because revocation is immediate. Stateless JWTs suit distributed services that cannot share a session store, but they cannot be revoked before expiry — so keep access tokens short-lived and pair them with rotating refresh tokens.
A session id is a reference the server can invalidate at will; a JWT is a self-contained claim the server has already agreed to trust until it expires.
| Server session | Stateless JWT | |
|---|---|---|
| Revocation | Immediate — delete the record | Not until expiry, without a blocklist |
| Storage | Session store (Redis, database) | None server-side |
| Scaling | Needs a shared store | Scales without shared state |
| Payload size | Small cookie | Larger; grows with claims |
| Best for | Web apps, admin panels, anything with logout | Service-to-service, short-lived access tokens |
The practical question: when an administrator disables an account, how long may that user keep working? If the answer is 'not at all', you need sessions or a token blocklist — which is a session store wearing a different hat.
In cookies with the right flags. localStorage is readable by any JavaScript on the page, which means one successful XSS or one compromised npm dependency exfiltrates every logged-in session.
cookies().set("session", token, {
httpOnly: true, // JavaScript cannot read it
secure: true, // HTTPS only
sameSite: "lax", // blocks most CSRF while allowing top-level navigation
path: "/",
maxAge: 60 * 60 * 24 * 7,
});Issue a short-lived access token (5–15 minutes) and a longer-lived refresh token. Every time the refresh token is used, invalidate it and issue a new one. If an old refresh token is ever presented again, that means it was stolen and replayed — revoke the entire token family and force re-authentication.
When a third party needs delegated access to your users' data, when you offer social login, or when an enterprise customer requires single sign-on. For your own first-party web app talking to your own API, OAuth adds machinery without adding security — a session cookie is simpler and safer.
If you do implement OAuth, follow the current OAuth 2.1 guidance: authorisation code flow with PKCE for all clients, no implicit flow, and exact redirect URI matching.
No, but it is frequently misused. The format is fine; the problems come from storing tokens in localStorage, skipping verification steps, and expecting revocation the design does not provide.
Balance risk against friction: 7–30 days with a sliding window suits consumer products, hours suit admin or financial tools. Always offer an explicit 'log out of all devices' action.
ROVQIX Engineering
Engineering team, ROVQIX
The ROVQIX engineering team builds and maintains web platforms, APIs and infrastructure for clients across SaaS, ecommerce and enterprise. These notes come out of real production work — deploys, incidents, migrations and audits.
ROVQIXdesigns and builds production web platforms — Next.js front ends, Node.js APIs and the infrastructure behind them. Tell us what you're building and we'll scope it with you.
Not a compliance document. This is the list we actually work through before a client site handles its first real user.
The question is not whether a secret will leak. It is whether you will know, and how long it takes to make the leaked one useless.
Middleware is the cheapest place to redirect a request and the most expensive place to make a database call. The matcher config is the whole game.
No spam. Just the occasional case study and craft breakdown.