CSRF
Official plugin — cross-site request forgery defense using fetch metadata (Sec-Fetch-Site) with an Origin fallback. Stateless: no tokens, no cookies, no session reads.
csrf() blocks browsers on other sites from mutating your state. It runs at onAuth — before gate policies and the flow body — using the fetch-metadata headers every modern browser sends, with an Origin check as the fallback. No tokens to mint, no cookies to double-submit, no session reads: the defense is stateless.
Quick start
import { oke } from "okengine";
import { csrf } from "okengine/plugins";
export const app = oke({ name: "shop", env: "dev" }).plug(csrf());GET/HEAD/OPTIONS always pass. A POST with Sec-Fetch-Site: cross-site now gets the gate element's typed Forbidden — unless its Origin is yours or allow-listed.
How it decides
Fetch metadata first
Sec-Fetch-Site: same-origin and none pass. same-site passes by default (your subdomains) — set allowSameSite: false when subdomains host untrusted content. cross-site falls through to the Origin check.
Origin fallback
With cross-site (or no metadata at all — older browsers), the Origin header must be the app's own origin or an entry in allowOrigins; anything else is Forbidden.
Headerless clients
Requests carrying neither header — curl, server-to-server calls, webhooks — are not browser CSRF vectors, so they pass by default. Set allowNoHeader: false to fail closed when your auth is purely cookie-based.
Options
| Option | Type | Default | Does |
|---|---|---|---|
allowOrigins | string[] | none | Absolute origins allowed to mutate cross-site (e.g. a separately-hosted admin) |
allowSameSite | boolean | true | Allow Sec-Fetch-Site: same-site (your subdomains) |
allowNoHeader | boolean | true | Allow mutating requests with neither metadata nor Origin |
Notes
| Behavior | Detail |
|---|---|
| Denial shape | The gate element's typed Forbidden (error.data.reason: "csrf") — same as any gate denial |
| Legacy browsers | No fetch metadata → the Origin check carries the defense |
| Token patterns | Double-submit tokens can layer on later for defense-in-depth; fetch metadata alone covers current browsers |
| Non-HTTP triggers | No-op — clock and signal flows are never browser-driven |
Runtime configuration
Allow-listed origins are exactly the config you want to change live. Pass a configSource() and the rules follow the database within one sync interval:
const rules = configSource({
plugin: "csrf",
code: { allowOrigins: ["https://admin.example.com"] },
db: { store: db },
kv: cache,
});
on(every("30s"), rules.sync());
export const app = oke({ name: "shop", env: "dev" }).plug(csrf(rules));See Plugins → Runtime configuration for the full contract.
Next
CORS
Official plugin — cross-origin rules at the edge: preflight OPTIONS answered even for paths bound to other methods, plus CORS headers on matched responses. Closed by default.
IP Allowlist
Official plugin — allow/deny rules by client IP at the edge of the pipeline, with the same typed Forbidden denial the gate element produces.