Bump versions for dev release. Clean up other mentions of config->{session}
[catagits/Catalyst-Plugin-Session.git] / lib / Catalyst / Plugin / Session / Tutorial.pod
1 =pod
2
3 =head1 NAME
4
5 Catalyst::Plugin::Session::Tutorial - Understanding and using sessions.
6
7 =head1 ASSUMPTIONS
8
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
13 =head1 WHAT ARE SESSIONS
14
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.
19
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
22 that.
23
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".
26
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.
29
30 =head1 HOW SESSIONS WORK
31
32 =head2 Cookies
33
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
36 request is made.
37
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.
41
42 Then, on every subsequent request the client will send a C<Cookie> header, with
43 the cookie name and value.
44
45 =head2 Cookie Alternatives
46
47 Another way is to make sure that the session ID is repeated is to include it in
48 every URI.
49
50 This can be as either a part of the path, or as a query parameter.
51
52 This technique has several issues which are discussed in
53 L<Catalyst::Plugin::Session::State::URI/CAVEATS>.
54
55 =head2 Server-Side Behavior
56
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.
60
61 =head1 USING SESSIONS
62
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>.
66
67 The plugin is modular, and requires backend plugins to be used.
68
69 =head2 State Plugins
70
71 State plugins handle session ID persistence. For example
72 L<Catalyst::Plugin::Session::State::Cookie> creates a cookie with the session
73 ID in it.
74
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.
78
79 =head2 Store Plugins
80
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
84 L<Cache::FastMmap>.
85
86 =head2 Configuration
87
88 First you need to load the appropriate plugins into your L<Catalyst>
89 application:
90
91     package MyApp;
92
93     use Catalyst qw/
94         Session
95         Session::State::Cookie
96         Session::Store::File
97     /;
98
99 This loads the session API, as well as the required backends of your choice.
100
101 After the plugins are loaded they need to be configured. This is done according
102 to L<Catalyst::Manual::Cookbook/Configure_your_application>.
103
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>.
107
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
110 look like this:
111
112     MyApp->config( 'Plugin::Session' => {
113         cookie_name => "my_fabulous_cookie",
114         storage     => "/path/to/store_data_file",
115     });
116
117 =head2 Usage
118
119 Now, let's say we have an online shop, and the user is adding an item to the
120 shopping cart.
121
122 Typically the item the user was viewing would have a form or link that adds the
123 item to the cart.
124
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.
127
128 Our C<add> action should look something like this:
129
130     package MyApp::Controller::Cart;
131
132     sub add : Local {
133         my ( $self, $c, $item_id, $quantity ) = @_;
134         $quantity ||= 1;
135
136         if ( $c->model("Items")->item_exists($item_id) ) {
137             $c->session->{cart}{$item_id} += $quantity;
138         } else {
139             die "No such item";
140         }
141     }
142
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
146 last request.
147
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
151 sent by the client).
152
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
156 request, if at all.
157
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.
161
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.
164
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.
167
168 Here is an action some Template Toolkit example code that could be used to
169 generate a cart listing:
170
171     sub list_cart : Local {
172         my ( $self, $c ) = @_;
173
174         # get the cart data, that maps from item_id to quantity
175         my $cart = $c->session->{cart} || {};
176
177         # this is our abstract model in which items are stored
178         my $storage = $c->model("Items");
179
180         # map from item_id to item (an object or hash reference)
181         my %items = map { $_ => $storage->get_item($_) } keys %$cart;
182
183         # put the relevant info on the stash
184         $c->stash->{cart}{items} = \%items;
185         $c->stash->{cart}{quantity} = $cart;
186     }
187
188 And [a part of] the template it forwards to:
189
190     <table>
191
192         <thead>
193             <tr>
194                 <th>Item</th>
195                 <th>Quantity</th>
196                 <th>Price</th>
197                 <th>remove</th>
198             </tr>
199         </thead>
200
201         <tbody>
202         [%# the table body lists all the items in the cart %]
203         [% FOREACH item_id = cart.items.keys %]
204
205             [%# each item has its own row in the table %]
206
207             [% item = cart.items.$item_id %]
208             [% quantity = cart.quantity.$item_id %]
209
210             <tr>
211                 <td>
212                     [%# item.name is an attribute in the item
213                       # object, as loaded from the store %]
214                     [% item.name %]
215                 </td>
216
217                 <td>
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 %]" />
222                 </td>
223
224                 <td> $ [% item.price * quantity %] </td>
225
226                 <td>
227                     <a href="[% c.uri_for('/cart/remove') %]/[% item_id %]">
228                         <img src="/static/trash_can.png" />
229                     </a>
230                 </td>
231         [% END %]
232         <tbody>
233
234         <tfoot>
235             <tr>
236                 <td colspan="2"> Total: </td>
237                 <td>
238                     [%# calculate sum in this cell - too
239                       # much headache for a tutorial ;-) %]
240                 </td>
241                 <td>
242                     <a href="[% c.uri_for('/cart/empty') %]">Empty cart</a>
243                 </td>
244             </tr>
245         </tfoot>
246
247     </table>
248
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.
253
254 =head1 SECURITY ISSUES
255
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.
259
260 =head2 (Not) Trusting the Client
261
262 In order to avoid the overhead of server-side data storage, the session data can
263 be included in the cookie itself.
264
265 There are two problems with this:
266
267 =over 4
268
269 =item 1
270
271 The user can change the data.
272
273 =item 2
274
275 Cookies have a 4 kilobyte size limit.
276
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.
281
282 This can be implemented by encrypting and signing the cookie data, but this is
283 a big headache.
284
285 =back
286
287 =head2 Session Hijacking
288
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.
291
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.
295
296 This is called session hijacking. There are several ways one might hijack
297 another user's session.
298
299 =head3 Cross Site Scripting
300
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.
304
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).
309
310 =head3 Social Engineering
311
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.
314
315 Also, a naive user could be tricked into showing the cookie data from the
316 browser to a malicious user.
317
318 =head1 AUTHOR
319
320 Yuval Kogman E<lt>nothingmuch@woobling.orgE<gt>
321
322 =cut