NAME
PAGI::Middleware::CSRF - Cross-Site Request Forgery protection middleware
SYNOPSIS
use PAGI::Middleware::Builder;
my $app = builder {
enable 'CSRF',
secret => 'your-secret-key',
token_header => 'X-CSRF-Token',
cookie_name => 'csrf_token',
safe_methods => ['GET', 'HEAD', 'OPTIONS'];
$my_app;
};
# Issue-only mode: the middleware never rejects; the app validates via
# $ctx->csrf_verify once it has parsed the submitted form/JSON params.
my $app2 = builder {
enable 'CSRF', secret => 'your-secret-key', enforce => 'app';
$my_app;
};
DESCRIPTION
PAGI::Middleware::CSRF provides protection against Cross-Site Request Forgery attacks by validating tokens on state-changing requests.
CONFIGURATION
secret (required)
Secret key used for token generation.
token_header (default: 'X-CSRF-Token')
Header name to look for the CSRF token.
cookie_name (default: 'csrf_token')
Cookie name for the CSRF token.
safe_methods (default: ['GET', 'HEAD', 'OPTIONS', 'TRACE'])
HTTP methods that don't require CSRF validation.
secure (default: 0)
Add the
Secureattribute to the CSRF cookie, restricting it to HTTPS requests. Off by default so plain-HTTP development setups keep working; for production HTTPS deployments, addsecure => 1.enforce (default: 'header')
How unsafe methods (anything not in
safe_methods) are checked:'header'- the middleware itself validates: the request must carry atoken_headerwhose value matches the cookie token, or the middleware responds 403 and the app is never called. This only works for requests that can set a custom header (typically AJAX/fetch); a plain HTML form POST has no way to add one, so a server-rendered form under this mode would always 403 -- see "USAGE" for why.'app'- issue-only. The middleware mints/persists the cookie token exactly as it does for safe methods, on every method, and never auto-rejects. It stashes the cookie token (the existing one, or a freshly minted one if none existed yet) into scope ascsrf_tokenfor the app to read via$ctx->csrf_token. The app owns validation, by calling$ctx->csrf_verify($submitted)once it has parsed the request's params, and decides the response for a failed check. This is what server-rendered form POSTs need.
USAGE
The CSRF middleware always uses a double-submit cookie pattern: a token is generated and stored in an HttpOnly cookie, and a request is only valid if it also carries that same token some other way -- because HttpOnly means client-side JavaScript cannot read the cookie itself (document.cookie won't show it, and neither would a hypothetical getCookie helper). That "some other way" is where the two enforce modes diverge.
Header flow (enforce => 'header', the default)
Use this for JSON/AJAX APIs, where the client can set a custom request header. The middleware validates the header itself; the app is never called on a mismatch.
Render the token into the page once (a <meta> tag is the usual spot), reading it from the context helper -- not from the cookie, which JavaScript cannot see:
<meta name="csrf-token" content="<%= $ctx->csrf_token %>">
Then have client-side script read the meta tag and send it back as the configured header:
const token = document.querySelector('meta[name="csrf-token"]').content;
fetch('/api/resource', {
method: 'POST',
headers: { 'X-CSRF-Token': token },
});
Form flow (enforce => 'app')
Use this for server-rendered HTML forms. A plain <form> POST has no way to add a custom header, so enforce = 'header'> would 403 every such submission -- that's precisely why this mode exists: the middleware only issues the token (on every method, including the POST itself) and never auto-rejects; the app validates once it has parsed the submitted params.
Embed the token as a hidden field:
<input type="hidden" name="_csrf_token" value="<%= $ctx->csrf_token %>">
Then, in the handler, verify the submitted value against the one the middleware stashed in scope:
return $ctx->text('CSRF token validation failed', status => 403)
unless $ctx->csrf_verify($params->{_csrf_token});
See "csrf_token" in PAGI::Context and "csrf_verify" in PAGI::Context for the helper reference.
SEE ALSO
PAGI::Middleware - Base class for middleware
"csrf_token" in PAGI::Context, "csrf_verify" in PAGI::Context - context helpers for reading and checking the token, used by the enforce => 'app' flow