add Catalyst::Plugin::Session::Tutorial
Yuval Kogman [Fri, 27 Jan 2006 19:09:48 +0000 (19:09 +0000)]
lib/Catalyst/Plugin/Session/Tutorial.pod [new file with mode: 0644]

diff --git a/lib/Catalyst/Plugin/Session/Tutorial.pod b/lib/Catalyst/Plugin/Session/Tutorial.pod
new file mode 100644 (file)
index 0000000..0bf101e
--- /dev/null
@@ -0,0 +1,325 @@
+=pod
+
+=head1 NAME
+
+Catalyst::Plugin::Session::Tutorial - Understanding and using sessions.
+
+=head1 ASSUMPTIONS
+
+This tutorial assumes that you are familiar with web applications in general
+and Catalyst specifically (up to models and configuration), that you know what
+HTTP is.
+
+=head1 WHAT ARE SESSIONS
+
+When users use a site, especially one that knows who they are (sites you log in
+to, sites which let you keep a shopping cart, etc), the server preparing the
+content has to know that request X comes from client A while request Y comes
+from client B, so that each user gets the content most appropriate for it.
+
+The problem is that HTTP is a stateless protocol. This means that every request
+is distinct, and even if it comes from the same client, it's difficult to know
+that.
+
+The way sessions are maintained between distinct requests is that the client
+says, for every request "I'm client A" or "I'm client B".
+
+This piece of data that tells the server "I'm X" is called the session ID, and
+the threading of several requests together is called a session.
+
+=head1 HOW SESSIONS WORK
+
+=head2 Cookies
+
+HTTP has a feature that lets this become easier, called cookies. A cookie is
+something the server asks the client to save somewhere, and resend every time a
+request is made.
+
+They way they work is that the server sends the C<Set-Cookie> header, with a
+cookie name, a value, and some meta data (like when it expires, what paths it
+applies to, etc). The client saves this.
+
+Then, on every subsequent request the client will send a C<Cookie> header, with
+the cookie name and value.
+
+=head2 Cookie Alternatives
+
+Another way is to make sure that the session ID is repeated is to include it in
+every URI.
+
+This can be as either a part of the path, or as a query parameter.
+
+This technique has several issues which are discussed in
+L<Catalyst::Plugin::Session::State::URI/CAVEATS>.
+
+=head2 Server-side Behavior
+
+When the server receives the session ID it can then look this key up into a
+database of some sort. For example the database can contain a shopping cart's
+contents, user preferences, etc.
+
+=head1 USING SESSIONS
+
+In L<Catalyst>, the L<Catalyst::Plugin::Session> plugin provides an API for
+convenient handling of session data. This API is based on the older, less
+flexible and less reliable L<Catalyst::Plugin::Session::FastMmap>.
+
+The plugin is modular, and requires backend plugins to be used.
+
+=head2 State Plugins
+
+State plugins handle session ID persistence. For example
+L<Catalyst::Plugin::Session::State::Cookie> creates a cookie with the session
+ID in it.
+
+These plugins will automatically set C<< $c->sessionid >> at the begining of
+the request, and automatically cause C<< $c->sessionid >> to be saved by the
+client at the end of the request.
+
+=head2 Store Plugins
+
+The backend into which session data is stored is provided by these plugins. For
+example, L<Catalyst::Plugin::Session::Store::DBI> uses a database table to
+store session data, while L<Catalyst::Plugin::Session::Store::FastMmap> uses
+L<Cache::FastMmap>.
+
+=head2 Configuration
+
+First you need to load the appropriate plugins into your L<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 L<Catalyst::Manual::Tutorial/Configuring>.
+
+Each backend plugin requires it's own configuration options (with most plugins
+providing sensible defaults). The session API itself also has configurable
+options listed in L<Catalyst::Plugin::Session/CONFIGURATION>.
+
+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 arond, it'll look
+like this:
+
+    MyApp->config( session => {
+        cookie_name => "my_fabulous_cookie",
+        storage     => "/path/to/store_data_file",
+    });
+
+=head2 Usage
+
+Now, let's say we have an online shop, and the user is adding an item to the
+shopping cart.
+
+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 C</cart/add/foo_baz/2>, meaning that we want two
+units of the item C<foo_baz> to be added to the cart.
+
+Our C<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";
+        }
+    }
+
+The way this works is that C<< $c->session >> always returns a hash reference
+to some data which is stored by the storage backend plugin. The hash reference
+returned always contains the same items that were in there at the end of the
+last request.
+
+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 then the store will be asked to retrieve the session
+data for that specific session ID, and this is returned from
+C<< $c->session >>. This retrieval is cached, and will only happen once per
+request, if at all.
+
+If a session ID is not set, then 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.
+
+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.
+
+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 it's 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>
+
+As you can see the way that items are added into C<< $c->session->{cart} >> is
+pretty simple. Since C<< $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.
+
+=head1 SECURITY ISSUES
+
+These issues all relate to how session data is managed, as described above.
+These are not issues you should be concerned about in your application code,
+but are here for their educational value.
+
+=head2 (Not) Trusting the Client
+
+In order to avoid the overhead of serverside data storage, the session data can
+be included in the cookie itself.
+
+There are two problems with this:
+
+=over 4
+
+=item 1
+
+The user can change the data.
+
+=item 2
+
+Cookies have a 4 kilobyte size limit.
+
+=back
+
+The size limit is of no concern in this section, but data changing is. In the
+database scheme the data can be trusted, since the user can neither read nor
+write it. However, if the data is delegated to the user, then special measures
+have to be added for ensuring data integrity, and perhaps secrecy too.
+
+This can be implemented by encrypting and signing the cookie data, but this is
+a big headache.
+
+=back
+
+=head2 Session Hijacking
+
+What happens when client B says "I'm client A"?  Well, basically, the server
+buys it. There's no real way around it.
+
+The solution is to make "I'm client A" a difficult thing to say. This is why
+session IDs are randomized. If they are properly randomized, session IDs are so
+hard to guess that they must be stolen instead.
+
+This is called session hijacking. There are several ways one might hijack
+another user's session.
+
+=head3 Cross Site Scripting
+
+One is by using cross site scripting attacks to steal the cookie data. In
+community sites, where users can cause the server to display arbitrary HTML,
+they can use this to put JavaScript code on the server.
+
+If the server does not enforce a strict subset of tags that may be used, the
+malicious user could use this code to steal the cookies (there is a javascript
+API that lets cookies be accessed, but this code has to be run on the same
+website that the cookie came from).
+
+=head3 Social Engineering
+
+By tricking a user into revealing a URI with session data embedded in it (when
+cookies are not used), 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.
+
+=head1 AUTHOR
+
+Yuval Kogman E<lt>nothingmuch@woobling.orgE<gt>
+
+=cut
+