Authentication patterns: sessions, JWTs and what to use when
The sessions-versus-JWT argument is really an argument about revocation. Decide how fast you need to be able to log someone out.
Cross-site request forgery tricks a logged-in user's browser into making a state-changing request to your site. SameSite=Lax cookies block most cross-site POST requests by default, but you still need explicit protection for cookie-authenticated APIs, cross-origin flows, and any endpoint accepting simple content types.
A user is logged into your site. They visit a malicious page, which submits a form to your domain. The browser attaches their session cookie automatically because that is how cookies work. Your server sees a valid session and performs the action — a transfer, a password change, a deletion.
<!-- On attacker.com, auto-submitted -->
<form action="https://yourbank.example/transfer" method="POST">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="10000">
</form>
<script>document.forms[0].submit()</script>The attacker cannot read the response — the same-origin policy prevents that. They do not need to. The side effect is the attack.
It tells the browser whether to attach a cookie on requests originating from another site.
| Value | Cookie sent on cross-site… | Trade-off |
|---|---|---|
| Strict | Never, including top-level navigation | Users arriving from an external link appear logged out |
| Lax | Only top-level GET navigation | Good balance; the common default |
| None | Always (requires Secure) | Needed for genuine cross-site use; no CSRF protection |
// Synchroniser token: server-generated, tied to the session
const token = crypto.randomBytes(32).toString("base64url");
session.csrfToken = token;
// Verify on every state-changing request, in constant time
const submitted = request.headers.get("x-csrf-token") ?? formData.get("_csrf");
if (!submitted || !timingSafeEqual(Buffer.from(submitted), Buffer.from(session.csrfToken))) {
return new Response("Invalid CSRF token", { status: 403 });
}Less common, but yes. Cross-site integrations, GET-based state changes, and misconfigured cookies all leave gaps, and high-value endpoints warrant explicit protection regardless.
Only if they authenticate with cookies. With a bearer token in an Authorization header, classic CSRF does not apply — though you then have to think about token storage instead.
No. CORS controls whether JavaScript can read a response. A form submission does not need to read anything, and forms are not subject to CORS.
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.
The sessions-versus-JWT argument is really an argument about revocation. Decide how fast you need to be able to log someone out.
React escapes text by default, which handles most XSS. The remaining cases are the ones people write deliberately.
A server action is a public HTTP endpoint with nicer syntax. Treat it like one and they are excellent; forget that and you have shipped an unauthenticated API.
No spam. Just the occasional case study and craft breakdown.