Mojolicious session cookies

By Abhijit Menon-Sen <ams@toroid.org>

2011-02-15

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 application code.

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.

The only sensible way to use cookies for authentication is to use HTTPS 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.