Sessions and security
Three keywords cover the web-security basics; each is implemented in C inside the dispatcher, so none of them add a Perl frame to the request.
Sessions
session
secret => secret('session_key'), # from the secrets system
expires => '7d',
samesite => 'Lax';
$c->session is then a hashref, written back to an HMAC-SHA256-signed
cookie when it changes:
post '/login' => sub {
my ($c) = @_;
$c->session->{user_id} = $user->id; # signed into the cookie
$c->redirect('/');
};
get '/me' => sub {
my ($c) = @_;
my $id = $c->session->{user_id} or return $c->redirect('/login');
$c->json({ id => $id });
};
post '/logout' => sub { my ($c) = @_; $c->session_expire; $c->redirect('/') };
Source the key from the secrets system,
never a literal. Options: secret, cookie (default punk.sid),
expires, path, domain, secure, httponly (default on),
samesite (default Lax). Also configurable from punk.yml.
CSRF
session secret => secret('session_key');
csrf;
Single-use tokens over the session: every unsafe request must carry a live token, and using one spends it.
get '/edit' => sub {
my ($c) = @_;
return $c->render('edit', { csrf => $c->csrf_field });
};
post '/edit' => sub {
my ($c) = @_; # only runs if the token was good
...;
};
<form method="post" action="/edit">
{% raw csrf %}
...
</form>
$c->csrf_field is the hidden input, $c->csrf_token the bare value;
the token is also mirrored into a script-readable cookie for fetch
calls. Options: keep (how many live tokens to allow) and exempt
(path prefixes to skip - webhooks, callbacks).
CORS
cors; # a public API: *, no credentials
cors origins => [ 'https://app.example.com' ], credentials => 1,
paths => [ '/api' ];
Handled inside the dispatcher: preflights are answered before routing -
no OPTIONS route needed - and the headers reach every response,
including the 404s and 405s that never build a context.
Access-Control-Allow-Methods comes from the router itself, so it
cannot promise a method the application does not serve.
The rest of the posture
- Controller targets, template engines and spec mounts all resolve at boot - a misconfigured app refuses to start rather than serve.
- Secrets are references resolved from outside the config file, shown
as
[redacted]everywhere - see Config and secrets. - Static and markdown mounts refuse
..path segments.