CSRF defense
Cross-site request forgery, what Olympus does, what you do
CSRF is an attack where a malicious site causes a logged-in user's browser to make a request to your app that has side effects (transfer money, change password, delete data).
What Olympus does
Kratos flows
Every login, registration, recovery, settings, and verification flow includes a CSRF token. Kratos generates it, embeds it in the flow's HTML form, and verifies it on submission.
You see this in Hera's forms: a hidden <input type="hidden" name="csrf_token" value="..."> is part of every form.
If the user submits without the token (or with a stale one), Kratos rejects: 400 Bad Request with csrf_violation.
OAuth2 authorization endpoint
OAuth2's defense is the state parameter: client generates a random value, sends it on /oauth2/auth, expects it back on the callback. If state doesn't match, your client rejects.
For public clients, PKCE is also a defense (the code_verifier is a secret the attacker doesn't have).
What you should do
Your app's mutation endpoints
API endpoints that change state (POST, PUT, DELETE) need protection if they accept cookie-based auth. Two patterns:
Pattern A: Bearer-only.
If your API only accepts Authorization: Bearer ... and never cookies, CSRF doesn't apply. The attacker's site can't read your token (cross-origin) and can't send it.
Pattern B: Cookies + CSRF token. If you use cookies (e.g., a same-origin BFF), implement a CSRF token. Options:
- Synchronizer token: server issues a token on first page load, stores in session, validates on each mutation.
- Double-submit cookie: server sets a cookie, client mirrors it in a header. Compare server-side. Simpler, stateless. Olympus's bridge sessions use this.
- SameSite=Strict cookies: not sent on cross-site requests at all. Caveat: breaks legitimate cross-tab flows.
Frontend frameworks
- Next.js Server Actions include built-in CSRF protection (origin check).
- React Router actions require manual implementation.
- HTMX has
hx-headersfor adding tokens.
Don't rely on Origin header alone
Origin and Referer can be missing in some browsers / contexts. Use them as a secondary check, never the only one.
SameSite cookie attribute
Olympus sets all auth cookies with SameSite=Lax:
- Cookie is sent on top-level GET navigations (so links to your app work).
- Cookie is NOT sent on cross-site POST/PUT/DELETE (so CSRF mostly impossible).
SameSite=Strict is stricter but breaks the OAuth2 redirect-back flow (Hydra → your app via 302 from another origin, cookies blocked).
SameSite=None would re-enable CSRF, only use with explicit Secure flag and only when iframe embedding is needed.
CORS is not CSRF defense
CORS controls JavaScript reading responses, not browser-issued requests. A <form> submit cross-origin is allowed by CORS but is still CSRF, you need a token.
Testing your CSRF defense
# From a different origin, attempt a mutation:
curl -X POST https://your-app.com/api/transfer \
-H "Cookie: session=stolen" \
-d "amount=1000"Should return 403 with csrf_violation.