Mojolicious comes with safe, easy-to-use session cookies out of the box.
You just write…
$self->session(key => "some value");
…and $self->session('key') will retrieve the value in subsequent
requests by the same client.
Session data are stored in a hash, which may contain anything subject to
a maximum size of 4KB. The hash is serialised and signed with a message
authentication code to form a tamper-proof session cookie which expires
after an hour by default. When the cookie is presented by a client, the
server can verify the signature without any stored state. Cookies that
fail signature verification are discarded before they ever reach the
Cookies for authentication
These properties make it simple to implement login/logout functionality.
If the user provides the correct password, their user id is stored in a
session cookie, and subsequent requests can use the value, knowing that
it cannot have been forged or modified without knowing the server's
private signing key (set at startup using $app->secret).
Two things must be kept in mind when using session cookies this way.
First, although the cookies are cryptographically signed, they are not
encrypted. The session data can be read by anyone who can see the
cookie. Second, the signature provides integrity protection, but cannot
protect against replay attacks. Anyone who can see another user's cookie
can reuse that cookie to impersonate the user.
from login until logout for authenticated sessions. Anything else (such
as an HTTPS-only login page, to name one popular "security" measure) is
necessarily insecure, as Firesheep demonstrated so graphically (not that
the problem was unknown earlier).
Mojolicious depends on a recent version of the IO::Socket::SSL module to
implement HTTPS (older versions are broken). Unfortunately, this module
is not shipped with core Perl, and so HTTPS doesn't work out of the box
with Mojolicious. Making it work is as simple as installing the module,
but nothing in the Mojolicious core would prevent you from leaking your
authentication cookies over an insecure transport layer. The latest Mojo
includes a small patch I submitted to make it possible to turn on the
"secure" flag on all session cookies, so that browsers will not submit
them over HTTP. The rest of the
configuration is up to you.
How it works
First a broad overview, then some implementation details.
If you set $self->session(user_id => 42), the next response sent
to the client will include a cookie named "mojolicious", whose value is
a serialised representation of the session data with HMAC-MD5 signature
appended, and a one-hour expiration time. When this cookie is presented
by the client on a subsequent request, its signature is checked early in
the request processing cycle. If the signature is valid, the session is
initialised by deserialising the cookie. The cookie's expiration time is
also advanced. The next response sent to the client will contain the new
cookie value (including any changes to the session data).
These responsibilities are divided between Mojolicious::Controller and
Mojolicious::Sessions. The former reads and writes cookies (signed and
unsigned), while the latter serialises and de-serialises session data.
Mojolicious (i.e., the application class) has a "sessions" attribute,
which is a Sessions object by default. Before dispatching requests,
Mojolicious::dispatch() calls sessions->load(), which calls
$c->signed_cookie('mojolicious') to retrieve the value of the request
cookie named mojolicious. If the cookie exists and has a valid
signature, signed_cookie returns its value, which is thawed using
Storable and stored in the stash, where $c->session() can read it.
At the other end of the request cycle, Controller::rendered()
calls $app->sessions->store(), which uses Storable to freeze the
session hash (if there is anything in it), and calls signed_cookie() to
set an updated Set-Cookie header on the response.
The big secret
The cookie signature consists of the HMAC-MD5 hash computed using the
value of the cookie (including its expiration time) and a secret key
known only to the server. If you are using session cookies, the first
thing to do is change the secret (which defaults to the application's
name). It would be best to set it to a
(suitably strong) random string
at startup, with the caveat that restarting the server would invalidate
existing session keys.