Cataly...utorial(3pm)

πŸ¦‹ Catalyst::Plugin::Session::Tutorial(3pm) β€” the manual page, but make it make sense

okay bestie. this is Catalyst::Plugin::Session::Tutorial(3pm) from the User Contributed Perl Documentation, and it's literally just here to help you understand and use sessions. sit down, grab a strawberry πŸ“, we're doing this together.


πŸ’– NAME πŸ’–

Catalyst::Plugin::Session::Tutorial β€” Understanding and using sessions.

like. the whole thing. sessions. understanding them. using them. that's the vibe. πŸ’…


✨ ASSUMPTIONS ✨

before we go any further, this tutorial is assuming some stuff about you, and honestly? fair. it assumes:

  • you're already familiar with web applications in general,
  • you know Catalyst specifically β€” and like, up to models and configuration level, not just the hello-world,
  • and you know what HTTP is.

if that's you, slay, keep reading. if it's not, no shade, go do those first, this page will still be here. 🫢


🌈 WHAT ARE SESSIONS 🌈

okay so picture this. users are using a site. and it's the kind of site that knows who they are β€” you know, sites you log in to, sites that let you keep a shopping cart, that whole genre. and here's the problem: the server that's preparing the content has to know that request X is coming from client A while request Y is coming from client B, so that each user gets their content and not somebody else's. 😭

and the issue is... HTTP is a stateless protocol. which means every single request is distinct, and even if it's coming from the same client, it's difficult to know that. it's giving "who are you again?" every time. πŸ’€

so the way sessions are maintained between distinct requests is that the client just says, for every request, "I'm client A" or "I'm client B". iconic behavior honestly.

and this piece of data that tells the server "I'm X"? that's called the session ID, and the threading of several requests together is called a session. periodt. πŸ’…


πŸ”₯ HOW SESSIONS WORK πŸ”₯

πŸͺ Cookies πŸͺ

HTTP has a feature that makes all of this easier, and it's called cookies. a cookie is something the server asks the client to save somewhere, and then resend every time a request is made. lowkey genius.

the way they work is that the server sends the Set-Cookie header, with a cookie name, a value, and some metadata (like when it expires, what paths it applies to, etc.). the client saves this. ✨

then, on every subsequent request, the client will send a Cookie header, with the cookie name and value. and that's it. that's the whole trick.

another way to make sure the session ID gets repeated is to just include it in every URI. no cookies, no problem.

this can be as either a part of the path, or as a query parameter.

⚠️ okay but this technique has several issues which are discussed in "CAVEATS" in Catalyst::Plugin::Session::State::URI. go read that before you commit to URI sessions, bestie, i'm being so serious.

πŸ–₯️ Server-Side Behavior πŸ–₯️

when the server receives the session ID it can then look this key up in a database of some sort. for example, that database can contain a shopping cart's contents, user preferences, etc. it's giving ✨organized✨.


πŸ‘‘ USING SESSIONS πŸ‘‘

in Catalyst, the Catalyst::Plugin::Session plugin provides an API for convenient handling of session data. and fun fact, this API is based on the older, less flexible and less reliable Catalyst::Plugin::Session::FastMmap. so like... we've evolved. we've grown. growth. 🌱

the plugin is modular, meaning it requires backend plugins to be used. you can't just raw-dog it, you need your backends. πŸ’…

πŸŽ€ State Plugins πŸŽ€

state plugins handle session ID persistence. for example, Catalyst::Plugin::Session::State::Cookie creates a cookie with the session ID in it. classic.

these plugins will automatically set $c->sessionid at the beginning of the request, and automatically cause $c->sessionid to be saved by the client at the end of the request. it's automatic. it's hands-off. it's the dream. 😌

πŸ—„οΈ Store Plugins πŸ—„οΈ

the backend into which session data is stored is provided by these plugins. for example, Catalyst::Plugin::Session::Store::DBI uses a database table to store session data, while Catalyst::Plugin::Session::Store::FastMmap uses Cache::FastMmap. pick your fighter. πŸ”₯

βš™οΈ Configuration βš™οΈ

first you need to load the appropriate plugins into your Catalyst application:

package MyApp;

use Catalyst qw/
    Session
    Session::State::Cookie
    Session::Store::File
/;

this loads the session API, as well as the required backends of your choice. 🫢

after the plugins are loaded they need to be configured. this is done according to "Configure_your_application" in Catalyst::Manual::Cookbook. iykyk.

each backend plugin requires its own configuration options (with most plugins providing sensible defaults, because they're thoughtful like that). the session API itself also has configurable options listed in "CONFIGURATION" in Catalyst::Plugin::Session.

for the plugins above we don't need any configuration at all β€” they should work out of the box. but suppose we did want to change some things around, it'll look like this:

MyApp->config( 'Plugin::Session' => {
    cookie_name => "my_fabulous_cookie",
    storage     => "/path/to/store_data_file",
});

and yes, my_fabulous_cookie is a real example name and i love that for us. πŸͺ✨

πŸ’ƒ Usage πŸ’ƒ

now, let's say we have an online shop, and the user is adding an item to the shopping cart. classic. iconic. everyday behavior.

typically the item the user was viewing would have a form or link that adds the item to the cart.

suppose this link goes to /cart/add/foo_baz/2, meaning that we want two units of the item "foo_baz" to be added to the cart.

our "add" action should look something like this:

package MyApp::Controller::Cart;

sub add : Local {
    my ( $self, $c, $item_id, $quantity ) = @_;
    $quantity ||= 1;

    if ( $c->model("Items")->item_exists($item_id) ) {
        $c->session->{cart}{$item_id} += $quantity;
    } else {
        die "No such item";
    }
}

so the way this works is that $c->session always returns a hash reference to some data which is stored by the storage backend plugin. and the hash reference returned always contains the same items that were in there at the end of the last request. memory? she has it. πŸ’…

all the mishmash described above is done automatically. first, the method looks to see if a session ID is set. this session ID will be set by the State plugin if appropriate, at the start of the request (e.g. by looking at the cookies sent by the client).

if a session ID is set, the store will be asked to retrieve the session data for that specific session ID, and this is returned from $c->session. this retrieval is cached, and will only happen once per request, if at all. efficient. we love her. ✨

if a session ID is not set, a new one is generated, a new anonymous hash is created and saved in the store with the session ID as the key, and the reference to the hash is returned. brand new baby session. 🍼

the action above takes this hash reference, and updates a nested hash within it, that counts quantity of each item as stored in the cart.

any cart-listing code can then look into the session data and use it to display the correct items, which will, of course, be remembered across requests. the ✨continuity✨ of it all.

here is an action some Template Toolkit example code that could be used to generate a cart listing:

sub list_cart : Local {
    my ( $self, $c ) = @_;

    # get the cart data, that maps from item_id to quantity
    my $cart = $c->session->{cart} || {};

    # this is our abstract model in which items are stored
    my $storage = $c->model("Items");

    # map from item_id to item (an object or hash reference)
    my %items = map { $_ => $storage->get_item($_) } keys %$cart;

    # put the relevant info on the stash
    $c->stash->{cart}{items} = \%items;
    $c->stash->{cart}{quantity} = $cart;
}

and [a part of] the template it forwards to:

<table>

    <thead>
        <tr>
            <th>Item</th>
            <th>Quantity</th>
            <th>Price</th>
            <th>remove</th>
        </tr>
    </thead>

    <tbody>
    [%# the table body lists all the items in the cart %]
    [% FOREACH item_id = cart.items.keys %]

        [%# each item has its own row in the table %]

        [% item = cart.items.$item_id %]
        [% quantity = cart.quantity.$item_id %]

        <tr>
            <td>
                [%# item.name is an attribute in the item
                  # object, as loaded from the store %]
                [% item.name %]
            </td>

            <td>
                [%# supposedly this is part of a form where you
                  # can update the quantity %]
                <input type="text" name="[% item_id %]_quantity"
                    value="[% quantity %]" />
            </td>

            <td> $ [% item.price * quantity %] </td>

            <td>
                <a href="[% c.uri_for('/cart/remove') %]/[% item_id %]">
                    <img src="/static/trash_can.png" />
                </a>
            </td>
    [% END %]
    <tbody>

    <tfoot>
        <tr>
            <td colspan="2"> Total: </td>
            <td>
                [%# calculate sum in this cell - too
                  # much headache for a tutorial ;-) %]
            </td>
            <td>
                <a href="[% c.uri_for('/cart/empty') %]">Empty cart</a>
            </td>
        </tr>
    </tfoot>

</table>

πŸ—‘οΈ yes that's a trash can png for the remove link. we love a themed asset. πŸ™ˆ

as you can see the way that items are added into $c->session->{cart} is pretty simple. since $c->session is restored as necessary, and contains data from previous requests by the same client, the cart can be updated as the user navigates the site pretty transparently. it just works. no cap. 🌸


girl help this page is long, so it comes in 2 parts and this was part 1 β€” more parts follow, don't go anywhere bestie πŸ’œ

🚨 SECURITY ISSUES 🚨

Okay bestie, these ones are all about how session data gets managed, like we talked about up above. Real talk: these are not the kind of thing you stress about in your application code, they're here purely for the educational vibes. Think of it as lore. ✨


πŸ™ˆ (Not) Trusting the Client

So the whole reason someone might wanna put session data inside the cookie itself is to dodge the overhead of storing it server-side. Understandable! Efficient! Also kind of a trap. 😭

There are two problems with this:

  1. The user can change the data. (Not me trusting the client πŸ’€)
  2. Cookies have a 4 kilobyte size limit.

The size limit is not the issue in this section, the data changing is. In the database scheme the data can be trusted, because the user can neither read it nor write it. But if you delegate the data to the user, then you have to add special measures to make sure the data stays intact β€” and maybe keep it secret too. πŸ‘‘

You can pull this off by encrypting and signing the cookie data, but honestly? It's a big headache. 🫠


πŸ•΅οΈ Session Hijacking

Okay so what happens when client B just walks in and says "I'm client A"? Well. Basically the server just buys it. There's no real way around that. That's it, that's the whole vibe. 😭

So the solution is to make saying "I'm client A" genuinely hard. That's literally why session IDs are randomized. If they're properly randomized, session IDs are so hard to guess that a hater has to steal them instead of guessing. ✨

And that right there is what we call session hijacking. There are several ways someone might hijack another user's session:

πŸ’» Cross Site Scripting

One way is using cross site scripting attacks to steal the cookie data. On community sites, where users can make the server display arbitrary HTML, they can use that to put JavaScript code on the server. 🧚

And if the server doesn't enforce a strict subset of tags that may be used, that malicious user could use this code to steal the cookies (there's a JavaScript API that lets cookies be accessed, but this code has to be run on the same website that the cookie came from). No cap. πŸ”₯

🎭 Social Engineering

By tricking a user into revealing a URI with session data embedded in it (this is the when-cookies-are-not-used situation, iykyk), the session ID can also be stolen. πŸŽ€

Also, a naive user could be tricked into showing the cookie data from the browser to a malicious user. Protect your friends, bestie. 🫢


πŸ‘‘ AUTHOR πŸ‘‘

Yuval Kogman <nothingmuch@woobling.org>


And that's a wrap on the whole tutorial, bestie!! πŸŽ‰ This page came to you courtesy of the Cataly...utorial(3pm) manual, perl v5.40.1, dated 2025-08-10, from User Contributed Perl Documentation. Go build something iconic with your sessions, and stay safe out there. Slay, periodt. πŸ’…πŸ’–πŸ¦‹

πŸ“¦ where this page comes from