5 Catalyst::Plugin::Session::Tutorial - Understanding and using sessions.
9 This tutorial assumes that you are familiar with web applications in
10 general and Catalyst specifically (up to models and configuration), and
11 that you know what HTTP is.
13 =head1 WHAT ARE SESSIONS
15 When users use a site, especially one that knows who they are (sites you log in
16 to, sites which let you keep a shopping cart, etc.), the server preparing the
17 content has to know that request X comes from client A while request Y comes
18 from client B, so that each user gets the content meant for them.
20 The problem is that HTTP is a stateless protocol. This means that every request
21 is distinct, and even if it comes from the same client, it's difficult to know
24 The way sessions are maintained between distinct requests is that the client
25 says, for every request, "I'm client A" or "I'm client B".
27 This piece of data that tells the server "I'm X" is called the session ID, and
28 the threading of several requests together is called a session.
30 =head1 HOW SESSIONS WORK
34 HTTP has a feature that lets this become easier, called cookies. A cookie is
35 something the server asks the client to save somewhere, and resend every time a
38 The way they work is that the server sends the C<Set-Cookie> header, with a
39 cookie name, a value, and some metadata (like when it expires, what paths it
40 applies to, etc.). The client saves this.
42 Then, on every subsequent request the client will send a C<Cookie> header, with
43 the cookie name and value.
45 =head2 Cookie Alternatives
47 Another way is to make sure that the session ID is repeated is to include it in
50 This can be as either a part of the path, or as a query parameter.
52 This technique has several issues which are discussed in
53 L<Catalyst::Plugin::Session::State::URI/CAVEATS>.
55 =head2 Server-Side Behavior
57 When the server receives the session ID it can then look this key up in a
58 database of some sort. For example the database can contain a shopping cart's
59 contents, user preferences, etc.
63 In L<Catalyst>, the L<Catalyst::Plugin::Session> plugin provides an API for
64 convenient handling of session data. This API is based on the older, less
65 flexible and less reliable L<Catalyst::Plugin::Session::FastMmap>.
67 The plugin is modular, and requires backend plugins to be used.
71 State plugins handle session ID persistence. For example
72 L<Catalyst::Plugin::Session::State::Cookie> creates a cookie with the session
75 These plugins will automatically set C<< $c->sessionid >> at the begining of
76 the request, and automatically cause C<< $c->sessionid >> to be saved by the
77 client at the end of the request.
81 The backend into which session data is stored is provided by these plugins. For
82 example, L<Catalyst::Plugin::Session::Store::DBI> uses a database table to
83 store session data, while L<Catalyst::Plugin::Session::Store::FastMmap> uses
88 First you need to load the appropriate plugins into your L<Catalyst>
95 Session::State::Cookie
99 This loads the session API, as well as the required backends of your choice.
101 After the plugins are loaded they need to be configured. This is done according
102 to L<Catalyst::Manual::Cookbook/Configure_your_application>.
104 Each backend plugin requires its own configuration options (with most plugins
105 providing sensible defaults). The session API itself also has configurable
106 options listed in L<Catalyst::Plugin::Session/CONFIGURATION>.
108 For the plugins above we don't need any configuration at all - they should work
109 out of the box, but suppose we did want to change some things around, it'll
112 MyApp->config( 'Plugin::Session' => {
113 cookie_name => "my_fabulous_cookie",
114 storage => "/path/to/store_data_file",
119 Now, let's say we have an online shop, and the user is adding an item to the
122 Typically the item the user was viewing would have a form or link that adds the
125 Suppose this link goes to C</cart/add/foo_baz/2>, meaning that we want two
126 units of the item C<foo_baz> to be added to the cart.
128 Our C<add> action should look something like this:
130 package MyApp::Controller::Cart;
133 my ( $self, $c, $item_id, $quantity ) = @_;
136 if ( $c->model("Items")->item_exists($item_id) ) {
137 $c->session->{cart}{$item_id} += $quantity;
143 The way this works is that C<< $c->session >> always returns a hash reference
144 to some data which is stored by the storage backend plugin. The hash reference
145 returned always contains the same items that were in there at the end of the
148 All the mishmash described above is done automatically. First, the method looks
149 to see if a session ID is set. This session ID will be set by the State plugin
150 if appropriate, at the start of the request (e.g. by looking at the cookies
153 If a session ID is set, the store will be asked to retrieve the session
154 data for that specific session ID, and this is returned from
155 C<< $c->session >>. This retrieval is cached, and will only happen once per
158 If a session ID is not set, a new one is generated, a new anonymous hash is
159 created and saved in the store with the session ID as the key, and the
160 reference to the hash is returned.
162 The action above takes this hash reference, and updates a nested hash within
163 it, that counts quantity of each item as stored in the cart.
165 Any cart-listing code can then look into the session data and use it to display
166 the correct items, which will, of course, be remembered across requests.
168 Here is an action some Template Toolkit example code that could be used to
169 generate a cart listing:
171 sub list_cart : Local {
172 my ( $self, $c ) = @_;
174 # get the cart data, that maps from item_id to quantity
175 my $cart = $c->session->{cart} || {};
177 # this is our abstract model in which items are stored
178 my $storage = $c->model("Items");
180 # map from item_id to item (an object or hash reference)
181 my %items = map { $_ => $storage->get_item($_) } keys %$cart;
183 # put the relevant info on the stash
184 $c->stash->{cart}{items} = \%items;
185 $c->stash->{cart}{quantity} = $cart;
188 And [a part of] the template it forwards to:
202 [%# the table body lists all the items in the cart %]
203 [% FOREACH item_id = cart.items.keys %]
205 [%# each item has its own row in the table %]
207 [% item = cart.items.$item_id %]
208 [% quantity = cart.quantity.$item_id %]
212 [%# item.name is an attribute in the item
213 # object, as loaded from the store %]
218 [%# supposedly this is part of a form where you
219 # can update the quantity %]
220 <input type="text" name="[% item_id %]_quantity"
221 value="[% quantity %]" />
224 <td> $ [% item.price * quantity %] </td>
227 <a href="[% c.uri_for('/cart/remove') %]/[% item_id %]">
228 <img src="/static/trash_can.png" />
236 <td colspan="2"> Total: </td>
238 [%# calculate sum in this cell - too
239 # much headache for a tutorial ;-) %]
242 <a href="[% c.uri_for('/cart/empty') %]">Empty cart</a>
249 As you can see the way that items are added into C<< $c->session->{cart} >> is
250 pretty simple. Since C<< $c->session >> is restored as necessary, and contains
251 data from previous requests by the same client, the cart can be updated as the
252 user navigates the site pretty transparently.
254 =head1 SECURITY ISSUES
256 These issues all relate to how session data is managed, as described above.
257 These are not issues you should be concerned about in your application code,
258 but are here for their educational value.
260 =head2 (Not) Trusting the Client
262 In order to avoid the overhead of server-side data storage, the session data can
263 be included in the cookie itself.
265 There are two problems with this:
271 The user can change the data.
275 Cookies have a 4 kilobyte size limit.
277 The size limit is of no concern in this section, but data changing is. In the
278 database scheme the data can be trusted, since the user can neither read nor
279 write it. However, if the data is delegated to the user, then special measures
280 have to be added for ensuring data integrity, and perhaps secrecy too.
282 This can be implemented by encrypting and signing the cookie data, but this is
287 =head2 Session Hijacking
289 What happens when client B says "I'm client A"? Well, basically, the server
290 buys it. There's no real way around it.
292 The solution is to make "I'm client A" a difficult thing to say. This is why
293 session IDs are randomized. If they are properly randomized, session IDs are so
294 hard to guess that they must be stolen instead.
296 This is called session hijacking. There are several ways one might hijack
297 another user's session.
299 =head3 Cross Site Scripting
301 One is by using cross site scripting attacks to steal the cookie data. In
302 community sites, where users can cause the server to display arbitrary HTML,
303 they can use this to put JavaScript code on the server.
305 If the server does not enforce a strict subset of tags that may be used, the
306 malicious user could use this code to steal the cookies (there is a JavaScript
307 API that lets cookies be accessed, but this code has to be run on the same
308 website that the cookie came from).
310 =head3 Social Engineering
312 By tricking a user into revealing a URI with session data embedded in it (when
313 cookies are not used), the session ID can also be stolen.
315 Also, a naive user could be tricked into showing the cookie data from the
316 browser to a malicious user.
320 Yuval Kogman E<lt>nothingmuch@woobling.orgE<gt>