NAME
Punk::Session - signed cookie sessions
SYNOPSIS
package MyApp;
use Punk;
session
secret => secret('session_key'), # from the secrets system
expires => '7d',
samesite => 'Lax';
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('/') };
DESCRIPTION
A session is a hashref carried in a cookie: the handler reads and writes $c->session, and at the end of the request Punk serializes it (JSON), signs it with HMAC-SHA256 and the configured secret, and sets the cookie - only when it actually changed. The client can read the contents but cannot forge them, so do not put secrets in a session; a tampered cookie is rejected and the session comes up empty.
There is no server-side store in this cut: the whole session lives in the cookie. A session that serializes over ~4KB croaks, pointing at where a server-side store would go.
THE KEYWORD
session secret => secret('key'), expires => '7d', secure => 1;
Enables sessions for the application. Options:
secret- the signing key (required for a real deployment); source it from the secrets system so it never sits in the code.cookie- the cookie name (defaultpunk.sid).expires- a lifetime like'7d'/'12h'/'30m'/ seconds; omitted means a session cookie (gone when the browser closes).path(default/),domain,secure,httponly(default on),samesite(defaultLax) - the cookie attributes.
It also reads from config/punk.yml under a session: block.
CONTEXT METHODS
session
The session hashref. Read and write it; it is written back to the cookie at the end of the request if it changed.
session_expire
Log out: empty the session and delete the cookie.
flash
flash_keep
One-request messages riding the session - see "FLASH".
FLASH
post '/save' => sub {
my ($c) = @_;
$c->flash(notice => 'Saved.'); # for the NEXT request
$c->redirect('/list');
};
get '/list' => sub {
my ($c) = @_;
$c->render('list', { flash => $c->flash });
};
A flash message lives exactly one request: set it, redirect, and the redirected-to page reads it once. It rides the session under one reserved key (punk.flash), so it shares the session's machinery - the signing, the change-detected write-back, and the ~4KB cap - and needs the session keyword configured.
$c->flash(key => $value, ...) sets messages for the next request; $c->flash('key') reads one of this request's inbound messages; a bare $c->flash returns the whole inbound hashref (possibly empty) - which is also how a template gets it, passed explicitly like any other render data. $c->flash_keep re-arms the inbound messages for one more request - the redirect-through-a-redirect case.
The rotation is the session's own change detection: the first flash call of a request moves the inbound hash out of the session, so the consuming response rewrites the cookie without it, while a request that never touches flash leaves it riding untouched. Setting and reading in the same request do not meet: reads see the previous request's messages, writes feed the next one's.
SEE ALSO
Punk, "cookie" in Punk::Context, "secret" in Punk.
AUTHOR
LNATION <email@lnation.org>
LICENSE AND COPYRIGHT
This software is Copyright (c) 2026 by LNATION <email@lnation.org>.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)