1Catalyst::Plugin::SessiUosne:r:TCuotnotrriiablu(t3e)d PeCraltaDloycsutm:e:nPtlautgiionn::Session::Tutorial(3)
2
3
4
6 Catalyst::Plugin::Session::Tutorial - Understanding and using sessions.
7
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.
12
14 When users use a site, especially one that knows who they are (sites
15 you log in to, sites which let you keep a shopping cart, etc.), the
16 server preparing the content has to know that request X comes from
17 client A while request Y comes from client B, so that each user gets
18 the content meant for them.
19
20 The problem is that HTTP is a stateless protocol. This means that every
21 request is distinct, and even if it comes from the same client, it's
22 difficult to know that.
23
24 The way sessions are maintained between distinct requests is that the
25 client says, for every request, "I'm client A" or "I'm client B".
26
27 This piece of data that tells the server "I'm X" is called the session
28 ID, and the threading of several requests together is called a session.
29
31 Cookies
32 HTTP has a feature that lets this become easier, called cookies. A
33 cookie is something the server asks the client to save somewhere, and
34 resend every time a request is made.
35
36 The way they work is that the server sends the "Set-Cookie" header,
37 with a cookie name, a value, and some metadata (like when it expires,
38 what paths it applies to, etc.). The client saves this.
39
40 Then, on every subsequent request the client will send a "Cookie"
41 header, with the cookie name and value.
42
43 Cookie Alternatives
44 Another way is to make sure that the session ID is repeated is to
45 include it in every URI.
46
47 This can be as either a part of the path, or as a query parameter.
48
49 This technique has several issues which are discussed in "CAVEATS" in
50 Catalyst::Plugin::Session::State::URI.
51
52 Server-Side Behavior
53 When the server receives the session ID it can then look this key up in
54 a database of some sort. For example the database can contain a
55 shopping cart's contents, user preferences, etc.
56
58 In Catalyst, the Catalyst::Plugin::Session plugin provides an API for
59 convenient handling of session data. This API is based on the older,
60 less flexible and less reliable Catalyst::Plugin::Session::FastMmap.
61
62 The plugin is modular, and requires backend plugins to be used.
63
64 State Plugins
65 State plugins handle session ID persistence. For example
66 Catalyst::Plugin::Session::State::Cookie creates a cookie with the
67 session ID in it.
68
69 These plugins will automatically set "$c->sessionid" at the beginning
70 of the request, and automatically cause "$c->sessionid" to be saved by
71 the client at the end of the request.
72
73 Store Plugins
74 The backend into which session data is stored is provided by these
75 plugins. For example, Catalyst::Plugin::Session::Store::DBI uses a
76 database table to store session data, while
77 Catalyst::Plugin::Session::Store::FastMmap uses Cache::FastMmap.
78
79 Configuration
80 First you need to load the appropriate plugins into your Catalyst
81 application:
82
83 package MyApp;
84
85 use Catalyst qw/
86 Session
87 Session::State::Cookie
88 Session::Store::File
89 /;
90
91 This loads the session API, as well as the required backends of your
92 choice.
93
94 After the plugins are loaded they need to be configured. This is done
95 according to "Configure_your_application" in
96 Catalyst::Manual::Cookbook.
97
98 Each backend plugin requires its own configuration options (with most
99 plugins providing sensible defaults). The session API itself also has
100 configurable options listed in "CONFIGURATION" in
101 Catalyst::Plugin::Session.
102
103 For the plugins above we don't need any configuration at all - they
104 should work out of the box, but suppose we did want to change some
105 things around, it'll look like this:
106
107 MyApp->config( 'Plugin::Session' => {
108 cookie_name => "my_fabulous_cookie",
109 storage => "/path/to/store_data_file",
110 });
111
112 Usage
113 Now, let's say we have an online shop, and the user is adding an item
114 to the shopping cart.
115
116 Typically the item the user was viewing would have a form or link that
117 adds the item to the cart.
118
119 Suppose this link goes to "/cart/add/foo_baz/2", meaning that we want
120 two units of the item "foo_baz" to be added to the cart.
121
122 Our "add" action should look something like this:
123
124 package MyApp::Controller::Cart;
125
126 sub add : Local {
127 my ( $self, $c, $item_id, $quantity ) = @_;
128 $quantity ||= 1;
129
130 if ( $c->model("Items")->item_exists($item_id) ) {
131 $c->session->{cart}{$item_id} += $quantity;
132 } else {
133 die "No such item";
134 }
135 }
136
137 The way this works is that "$c->session" always returns a hash
138 reference to some data which is stored by the storage backend plugin.
139 The hash reference returned always contains the same items that were in
140 there at the end of the last request.
141
142 All the mishmash described above is done automatically. First, the
143 method looks to see if a session ID is set. This session ID will be set
144 by the State plugin if appropriate, at the start of the request (e.g.
145 by looking at the cookies sent by the client).
146
147 If a session ID is set, the store will be asked to retrieve the session
148 data for that specific session ID, and this is returned from
149 "$c->session". This retrieval is cached, and will only happen once per
150 request, if at all.
151
152 If a session ID is not set, a new one is generated, a new anonymous
153 hash is created and saved in the store with the session ID as the key,
154 and the reference to the hash is returned.
155
156 The action above takes this hash reference, and updates a nested hash
157 within it, that counts quantity of each item as stored in the cart.
158
159 Any cart-listing code can then look into the session data and use it to
160 display the correct items, which will, of course, be remembered across
161 requests.
162
163 Here is an action some Template Toolkit example code that could be used
164 to generate a cart listing:
165
166 sub list_cart : Local {
167 my ( $self, $c ) = @_;
168
169 # get the cart data, that maps from item_id to quantity
170 my $cart = $c->session->{cart} || {};
171
172 # this is our abstract model in which items are stored
173 my $storage = $c->model("Items");
174
175 # map from item_id to item (an object or hash reference)
176 my %items = map { $_ => $storage->get_item($_) } keys %$cart;
177
178 # put the relevant info on the stash
179 $c->stash->{cart}{items} = \%items;
180 $c->stash->{cart}{quantity} = $cart;
181 }
182
183 And [a part of] the template it forwards to:
184
185 <table>
186
187 <thead>
188 <tr>
189 <th>Item</th>
190 <th>Quantity</th>
191 <th>Price</th>
192 <th>remove</th>
193 </tr>
194 </thead>
195
196 <tbody>
197 [%# the table body lists all the items in the cart %]
198 [% FOREACH item_id = cart.items.keys %]
199
200 [%# each item has its own row in the table %]
201
202 [% item = cart.items.$item_id %]
203 [% quantity = cart.quantity.$item_id %]
204
205 <tr>
206 <td>
207 [%# item.name is an attribute in the item
208 # object, as loaded from the store %]
209 [% item.name %]
210 </td>
211
212 <td>
213 [%# supposedly this is part of a form where you
214 # can update the quantity %]
215 <input type="text" name="[% item_id %]_quantity"
216 value="[% quantity %]" />
217 </td>
218
219 <td> $ [% item.price * quantity %] </td>
220
221 <td>
222 <a href="[% c.uri_for('/cart/remove') %]/[% item_id %]">
223 <img src="/static/trash_can.png" />
224 </a>
225 </td>
226 [% END %]
227 <tbody>
228
229 <tfoot>
230 <tr>
231 <td colspan="2"> Total: </td>
232 <td>
233 [%# calculate sum in this cell - too
234 # much headache for a tutorial ;-) %]
235 </td>
236 <td>
237 <a href="[% c.uri_for('/cart/empty') %]">Empty cart</a>
238 </td>
239 </tr>
240 </tfoot>
241
242 </table>
243
244 As you can see the way that items are added into "$c->session->{cart}"
245 is pretty simple. Since "$c->session" is restored as necessary, and
246 contains data from previous requests by the same client, the cart can
247 be updated as the user navigates the site pretty transparently.
248
250 These issues all relate to how session data is managed, as described
251 above. These are not issues you should be concerned about in your
252 application code, but are here for their educational value.
253
254 (Not) Trusting the Client
255 In order to avoid the overhead of server-side data storage, the session
256 data can be included in the cookie itself.
257
258 There are two problems with this:
259
260 1. The user can change the data.
261
262 2. Cookies have a 4 kilobyte size limit.
263
264 The size limit is of no concern in this section, but data changing
265 is. In the database scheme the data can be trusted, since the user
266 can neither read nor write it. However, if the data is delegated to
267 the user, then special measures have to be added for ensuring data
268 integrity, and perhaps secrecy too.
269
270 This can be implemented by encrypting and signing the cookie data,
271 but this is a big headache.
272
273 Session Hijacking
274 What happens when client B says "I'm client A"? Well, basically, the
275 server buys it. There's no real way around it.
276
277 The solution is to make "I'm client A" a difficult thing to say. This
278 is why session IDs are randomized. If they are properly randomized,
279 session IDs are so hard to guess that they must be stolen instead.
280
281 This is called session hijacking. There are several ways one might
282 hijack another user's session.
283
284 Cross Site Scripting
285
286 One is by using cross site scripting attacks to steal the cookie data.
287 In community sites, where users can cause the server to display
288 arbitrary HTML, they can use this to put JavaScript code on the server.
289
290 If the server does not enforce a strict subset of tags that may be
291 used, the malicious user could use this code to steal the cookies
292 (there is a JavaScript API that lets cookies be accessed, but this code
293 has to be run on the same website that the cookie came from).
294
295 Social Engineering
296
297 By tricking a user into revealing a URI with session data embedded in
298 it (when cookies are not used), the session ID can also be stolen.
299
300 Also, a naive user could be tricked into showing the cookie data from
301 the browser to a malicious user.
302
304 Yuval Kogman <nothingmuch@woobling.org>
305
306
307
308perl v5.32.0 2020-07C-a2t8alyst::Plugin::Session::Tutorial(3)