CSRF Protection
The strategy
Latte web uses two layers to defend against cross-site request forgery:
SameSite=Strictcookies — the underlying HTTP server sets session cookies withSameSite=Strictso browsers won’t include them on cross-origin requests in the first placeOriginheader validation — theOriginChecksmiddleware rejects unsafe-method requests whoseOriginheader doesn’t match the server
There are no double-submit tokens or hidden form fields to manage. The cookies and the Origin header do the work together.
Default install
The simplest install derives the allowed origin from the request itself:
import module org.lattejava.web;
web.install(new OriginChecks());
For each POST, PUT, PATCH, or DELETE, the middleware compares the Origin header to the request’s own scheme + host + port. A match passes. A mismatch — or a missing Origin header on a browser request — gets rejected.
GET, HEAD, and OPTIONS requests are considered safe and pass through without an Origin check.
Explicit allow-list
When your app is reachable under multiple origins (apex + www, multiple subdomains, a CDN), pass the full list explicitly:
import module java.base;
import module org.lattejava.web;
web.install(new OriginChecks(List.of(
URI.create("https://example.com"),
URI.create("https://www.example.com"),
URI.create("https://app.example.com")
)));
A request whose Origin matches any URI in the list passes.
When to install
OriginChecks should sit early in the global middleware chain, just after SecurityHeaders. Installing it once on the root Web covers every route, which is what you want — CSRF defense is a default-on protection, not a per-endpoint opt-in.
web.install(SecurityHeaders.defaults());
web.install(new OriginChecks());
web.install(OIDC.sessionEndpoints(config));
Limitations
OriginChecksdoesn’t validate theRefererheader. Modern browsers sendOriginon all unsafe-method requests; if you also need to defend against very old clients you can layer aReferercheck yourself- It doesn’t apply to safe methods. If you have a side-effecting
GET(you shouldn’t), make it aPOST - It doesn’t replace authentication. CSRF protection only matters once the request also presents valid credentials