NAME¶
CGI::Session::Tutorial - Extended CGI::Session manual
STATE MAINTENANCE OVERVIEW¶
Since HTTP is a stateless protocol, each subsequent click to a web site is
treated as new request by the Web server. The server does not relate a visit
with a previous one, thus all the state information from the previous requests
are lost. This makes creating such applications as shopping carts, web sites
requiring users to authenticate, impossible. So people had to do something
about this despair situation HTTP was putting us in.
For our rescue come such technologies as
HTTP Cookies and
QUERY_STRINGs that help us save the users' session for a certain
period. Since
HTTP Cookies and
QUERY_STRINGs alone cannot take
us too far (
RFC 2965, Section 5, "Implementation
Limitations"), several other libraries have been developed to extend
their capabilities and promise a more reliable solution. CGI::Session is one
of them.
Before we discuss this library, let's look at some alternative solutions.
COOKIE¶
Cookie is a piece of text-information that a web server is entitled to place in
the user's hard disk, assuming a user agent (such as Internet Explorer,
Mozilla, etc) is compatible with the specification. After the cookie is
placed, user agents are required to send these cookies back to the server as
part of the HTTP request. This way the server application ( CGI, for example )
will have a way of relating previous requests by the same user agent, thus
overcoming statelessness of HTTP.
Although
HTTP Cookies seem to be promising solution for the statelessness
of HTTP, they do carry certain limitations, such as limited number of cookies
per domain and per user agent and limited size on each cookie. User Agents are
required to store at least 300 cookies at a time, 20 cookies per domain and
allow 4096 bytes of storage for each cookie. They also rise several Privacy
and Security concerns, the lists of which can be found on the sections
6-"Privacy" and
7-"Security Considerations"
of
RFC 2965.
QUERY STRING¶
Query string is a string appended to URL following a question mark (?) such as:
http://my.dot.com/login.cgi?user=sherzodr;password=top-secret
As you probably guessed, it can also help you pass state information from a
click to another, but how secure is it do you think, considering these URLs
tend to get cached by most of the user agents and also logged in the servers
access log, to which everyone can have access.
HIDDEN FIELDS¶
Hidden field is another alternative to using query strings and they come in two
flavors: hidden fields used in POST methods and the ones in GET. The ones used
in GET methods will turn into a true query strings once submitted, so all the
disadvantages of QUERY_STRINGs apply. Although POST requests do not have
limitations of its sister-GET, the pages that hold them get cached by Web
browser, and are available within the source code of the page (obviously).
They also become unwieldily to manage when one has oodles of state information
to keep track of ( for instance, a shopping cart or an advanced search
engine).
Query strings and hidden fields are also lost easily by closing the browser, or
by clicking the browser's "Back" button.
SERVER SIDE SESSION MANAGEMENT¶
This technique is built upon the aforementioned technologies plus a server-side
storage device, which saves the state data on the server side. Each session
has a unique id associated with the data in the server. This id is also
associated with the user agent either in the form of a
HTTP Cookie, a
QUERY_STRING, hidden field or any combination of the above. This is
necessary to make the connection with the client and his data.
Advantages:
- •
- We no longer need to depend on User Agent constraints in cookie size.
- •
- Sensitive data no longer need to be traveling across the network at each
request (which is the case with query strings, cookies and hidden fields).
The only thing that travels is the unique id generated for the session (
5767393932698093d0b75ef614376314, for instance), which should make
no sense to third parties.
- •
- User will not have sensitive data stored in his/her computer in unsecured
file (which is a cookie file).
- •
- It's possible to handle very big and even complex data structures
transparently (which HTTP Cookies do not handle).
That's what CGI::Session is all about - implementing server side session
management. Now is a good time to get feet wet.
PROGRAMMING STYLE¶
Server side session management system might be seeming awfully convoluted if you
have never dealt with it. Fortunately, with CGI::Session all the complexity is
handled by the library transparently. This section of the manual can be
treated as an introductory tutorial to both logic behind session management,
and to CGI::Session programming style.
All applications making use of server side session management rely on the
following pattern of operation regardless of the way the system is
implemented:
- 1.
- Check if the user has session cookie dropped in his computer from previous
request
- 2.
- If the cookie does not exist, create a new session identifier, and drop it
as cookie to the user's computer.
- 3.
- If session cookie exists, read the session ID from the cookie and load any
previously saved session data from the server side storage. If session had
any expiration date set it's useful to re-drop the same cookie to the
user's computer so its expiration time will be reset to be relative to
user's last activity time.
- 4.
- Store any necessary data in the session that you want to make available
for the next HTTP request.
CGI::Session will handle all of the above steps. All you have to do is to choose
what to store in the session.
GETTING STARTED¶
To make CGI::Session's functionality available in your program do either of the
following somewhere on top of your program file:
use CGI::Session;
# or
require CGI::Session;
Whenever you're ready to create a new session in your application, do the
following:
$session = CGI::Session->new () or die CGI::Session->errstr;
Above line will first try to re-initialize an existing session by consulting
cookies and necessary QUERY_STRING parameters. If it fails will create a brand
new session with a unique ID, which is normally called
session ID,
SID for short, and can be accessed through
id() - object method.
We didn't check for any session cookies above, did we? No, we didn't, but
CGI::Session did. It looked for a cookie called "CGISESSID", and if
it found it tried to load existing session from server side storage (
file in our case). If cookie didn't exist it looked for a QUERY_STRING
parameter called "CGISESSID". If all the attempts to recover session
ID failed, it created a new session.
NOTE: For the above syntax to work as intended your application needs to have
write access to your computer's
TEMPDIR folder, which is usually
/tmp in UNIX. If it doesn't, or if you wish to store this application's
session files in a different place, you may pass the third argument like so:
$session = CGI::Session->new(undef, undef, {Directory=>'../tmp/sessions'});
Now it will store all the newly created sessions in (and will attempt to
initialize requested sessions from) that folder. Don't worry if the directory
hierarchy you want to use doesn't already exist. It will be created for you.
For details on how session data are stored refer to
CGI::Session::Driver::file, which is the default driver used in our above
example.
There is one small, but very important thing your application needs to perform
after creating CGI::Session object as above. It needs to drop Session ID as an
HTTP cookie into the user's computer. CGI::Session will use this cookie
to identify the user at his/her next request and will be able to load his/her
previously stored session data.
To make sure CGI::Session will be able to read your cookie at next request you
need to consult its "name()" method for cookie's suggested name:
$cookie = $query->cookie( -name => $session->name,
-value => $session->id );
print $query->header( -cookie=>$cookie );
"name()" returns "CGISESSID" by default. If you prefer a
different cookie name, you can change it as easily too, but you have to do it
before CGI::Session object is created:
CGI::Session->name("SID");
$session = CGI::Session->new();
Baking the cookie wasn't too difficult, was it? But there is an even easier way
to send a cookie using CGI::Session:
print $session->header();
The above will create the cookie using CGI::Cookie and will return proper http
headers using CGI.pm's CGI method. Any arguments to CGI::Session will be
passed to
CGI::header().
Of course, this method of initialization will only work if client is accepting
cookies. If not you would have to pass session ID in each URL of your
application as QUERY_STRING. For CGI::Session to detect it the name of the
parameter should be the same as returned by
name():
printf ("<a href=\"$ENV{SCRIPT_NAME}?%s=%s\">click me</a>", $session->name, $session->id);
If you already have session id to be initialized you may pass it as the only
argument, or the second argument of multi-argument syntax:
$session = CGI::Session->new( $sid );
$session = CGI::Session->new( "serializer:freezethaw", $sid );
$session = CGI::Session->new( "driver:mysql", $sid, {Handle=>$dbh} );
By default CGI::Session uses standard CGI to parse queries and cookies. If you
prefer to use a different, but compatible object you can pass that object in
place of $sid:
$cgi = CGI::Simple->new();
$session = CGI::Session->new( $cgi );
$session = CGI::Session->new( "driver:db_file;serializer:storable", $cgi);
# etc
See CGI::Simple
STORING DATA¶
CGI::Session offers
param() method, which behaves exactly as CGI.pm's
param() with identical syntax.
param() is used for storing data
in session as well as for accessing already stored data.
Imagine your customer submitted a login form on your Web site. You, as a good
host, wanted to remember the guest's name, so you can a) greet him accordingly
when he visits your site again, or b) to be helpful by filling out
user
name part of his login form, so the customer can jump right to the
password field without having to type his username again.
my $name = $cgi->param('username');
$session->param('username', $name);
Notice, we're grabbing
username value of the field using CGI.pm's (or
another compatible library's) "param()" method, and storing it in
session using CGI::Session's
param() method.
If you have too many stuff to transfer into session, you may find yourself
typing the above code over and over again. I've done it, and believe me, it
gets very boring too soon, and is also error-prone. So we introduced the
following handy method:
$session->save_param(['name']);
If you wanted to store multiple form fields just include them all in the second
list:
$session->save_param(['name', 'email']);
If you want to store all the available
QUERY_STRING parameters you can
omit the arguments:
$session->save_param();
See
save_param() for more details.
When storing data in the session you're not limited to strings. You can store
arrays, hashes and even most objects. You will need to pass them as references
(except objects).
For example, to get all the selected values of a scrolling list and store it in
the session:
my @fruits = $cgi->param('fruits');
$session->param('fruits', \@fruits);
For parameters with multiple values
save_param() will do the right thing
too. So the above is the same as:
$session->save_param($cgi, ['fruits']);
All the updates to the session data using above methods will not reflect in the
data store until your application exits, or $session goes out of scope. If,
for some reason, you need to commit the changes to the data store before your
application exits you need to call
flush() method:
$session->flush();
I've written a lot of code, and never felt need for using "flush()"
method, since CGI::Session calls this method at the end of each request. There
are, however, occasions I can think of one may need to call
flush().
ACCESSING STORED DATA¶
There's no point of storing data if you cannot access it. You can access stored
session data by using the same
param() method you once used to store
them. Remember the Username field from the previous section that we stored in
the session? Let's read it back so we can partially fill the Login form for
the user:
$name = $session->param("name");
printf "<input type=\"text\" name=\"name\" value=\"%s\" />", $name;
To retrieve previously stored @fruits do not forget to de reference it:
@fruits = @{ $session->param('fruits') };
Very frequently, you may find yourself having to create pre-filled and
pre-selected forms, like radio buttons, checkboxes and drop down menus
according to the user's preferences or previous action. With text and
textareas it's not a big deal - you can simply retrieve a single parameter
from the session and hard code the value into the text field. But how would
you do it when you have a group of radio buttons, checkboxes and scrolling
lists? For this purpose, CGI::Session provides
load_param() method,
which loads given session parameters to a CGI object (assuming they have been
previously saved with
save_param() or alternative):
$session->load_param($cgi, ["fruits"]);
Now when you say:
print $cgi->checkbox_group(fruits=>['apple', 'banana', 'apricot']);
See
load_param() for details.
Generated checkboxes will be pre-filled using previously saved information.
If you're making use of HTML::Template to separate the code from the skin, you
can as well associate CGI::Session object with HTML::Template and access all
the parameters from within HTML files. We love this trick!
$template = HTML::Template->new(filename=>"some.tmpl", associate=>$session);
print $template->output();
Assuming the session object stored "first_name" and "email"
parameters while being associated with HTML::Template, you can access those
values from within your "some.tmpl" file now:
Hello <a href="mailto:<TMPL_VAR email>"> <TMPL_VAR first_name> </a>!
See HTML::Template's online manual for details.
CLEARING SESSION DATA¶
You store session data, you access session data and at some point you will want
to clear certain session data, if not all. For this purpose CGI::Session
provides
clear() method which optionally takes one argument as an
arrayref indicating which session parameters should be deleted from the
session object:
$session->clear(["~logged-in", "email"]);
Above line deletes "~logged-in" and "email" session
parameters from the session. And next time you say:
$email = $session->param("email");
it returns undef. If you omit the argument to
clear(), be warned that all
the session parameters you ever stored in the session object will get deleted.
Note that it does not delete the session itself. Session stays open and
accessible. It's just the parameters you stored in it gets deleted
See
clear() for details.
DELETING A SESSION¶
If there's a start there's an end. If session could be created, it should be
possible to delete it from the disk for good:
$session->delete();
The above call to
delete() deletes the session from the disk for good. Do
not confuse it with
clear(), which only clears certain session
parameters but keeps the session open.
See
delete() for details.
EXPIRATION¶
CGI::Session provides limited means to expire sessions. Expiring a session is
the same as deleting it via
delete(), but deletion takes place
automatically. To expire a session, you need to tell the library how long the
session would be valid after the last access time. When that time is met,
CGI::Session refuses to retrieve the session. It deletes the session and
returns a brand new one. To assign expiration ticker for a session, use
expire():
$session->expire(3600); # expire after 3600 seconds
$session->expire('+1h'); # expire after 1 hour
$session->expire('+15m'); # expire after 15 minutes
$session->expire('+1M'); # expire after a month and so on.
When session is set to expire at some time in the future, but session was not
requested at or after that time has passed it will remain in the disk. When
expired session is requested CGI::Session will remove the data from disk, and
will initialize a brand new session.
See
expire() for details.
Before CGI::Session 4.x there was no way of intercepting requests to expired
sessions. CGI::Session 4.x introduced new kind of constructor,
load(),
which is identical in use to
new(), but is not allowed to create
sessions. It can only load them. If session is found to be expired, or session
does not exist it will return an empty CGI::Session object. And if session is
expired, in addition to being empty, its status will also be set to expired.
You can check against these conditions using
empty() and
is_expired() methods. If session was loaded successfully object
returned by "load()" is as good a session as the one returned by
"new()":
$session = CGI::Session->load() or die CGI::Session->errstr;
if ( $session->is_expired ) {
die "Your session expired. Please refresh your browser to re-start your session";
}
if ( $session->is_empty ) {
$session = $session->new();
}
Above example is worth an attention. Remember, all expired sessions are empty
sessions, but not all empty sessions are expired sessions. Following this rule
we have to check with "is_expired()" before checking with
"is_empty()". There is another thing about the above example. Notice
how its creating new session when un existing session was requested? By
calling "new()" as an object method! Handy thing about that is, when
you call "new()" on a session object new object will be created
using the same configuration as the previous object.
For example:
$session = CGI::Session->load("driver:mysql;serializer:storable", undef, {Handle=>$dbh});
if ( $session->is_expired ) {
die "Your session is expired. Please refresh your browser to re-start your session";
}
if ( $session->is_empty ) {
$session = $session->new();
}
Initial $session object was configured with
mysql as the driver,
storable as the serializer and
$dbh as the
database handle. Calling " new() " on this object will return an
object of the same configuration. So $session object returned from "
new() " in the above example will use
mysql as the driver,
storable as the serializer and
$dbh as the
database handle.
See
is_expired(),
is_empty(),
load() for details.
Sometimes it makes perfect sense to expire a certain session parameter, instead
of the whole session. I usually do this in my login enabled sites, where after
the user logs in successfully, I set his/her "_logged_in" session
parameter to true, and assign an expiration ticker on that flag to something
like 30 minutes. It means, after 30 idle minutes CGI::Session will clear
"_logged_in" flag, indicating the user should log in over again. I
agree, the same effect can be achieved by simply
expiring() the session
itself, but by doing this we would loose other session parameters, such as
user's shopping cart, session-preferences and the like.
This feature can also be used to simulate layered authentication, such as, you
can keep the user's access to his/her personal profile information for as long
as 60 minutes after a successful login, but expire his/her access to his
credit card information after 5 idle minutes. To achieve this effect, we will
use
expire() method again:
$session->expire(_profile_access, '1h');
$session->expire(_cc_access, '5m');
With the above syntax, the person will still have access to his personal
information even after 5 idle hours. But when he tries to access or update
his/her credit card information, he may be displayed a "login again,
please" screen.
See
expire() for details.
This concludes our discussion of CGI::Session programming style. The rest of the
manual covers some "SECURITY" issues. Driver specs from the previous
manual were moved to CGI::Session::Driver.
SECURITY¶
"How secure is using CGI::Session?", "Can others hack down
people's sessions using another browser if they can get the session id of the
user?", "Are the session ids easy to guess?" are the questions
I find myself answering over and over again.
STORAGE¶
Security of the library does in many aspects depend on the implementation. After
making use of this library, you no longer have to send all the information to
the user's cookie except for the session id. But, you still have to store the
data in the server side. So another set of questions arise, can an evil person
get access to session data in your server, even if he does, can he make sense
out of the data in the session file, and even if he can, can he reuse the
information against a person who created that session. As you see, the answer
depends on yourself who is implementing it.
- •
- First rule of thumb, do not store users' passwords or other sensitive data
in the session, please. If you have to, use one-way encryption, such as
md5, or SHA-1-1. For my own experience I can assure you that in properly
implemented session-powered Web applications there is never a need for
it.
- •
- Default configuration of the driver makes use of Data::Dumper class to
serialize data to make it possible to save it in the disk. Data::Dumper's
result is a human readable data structure, which, if opened, can be
interpreted easily. If you configure your session object to use either
Storable or FreezeThaw as a serializer, this would make it more difficult
for bad guys to make sense out of session data. But don't use this as the
only precaution. Since evil fingers can type a quick program using
Storable or FreezeThaw to decipher session files very easily.
- •
- Do not allow anyone to update contents of session files. If you're using
default serializer serialized data string needs to be eval()ed to
bring the original data structure back to life. Of course, we use Safe to
do it safely, but your cautiousness does no harm either.
- •
- Do not keep sessions open for very long. This will increase the
possibility that some bad guy may have someone's valid session id at a
given time (acquired somehow). To do this use expire() method to
set expiration ticker. The more sensitive the information on your Web site
is, the sooner the session should be set to expire.
SESSION IDs¶
Session ids are not easily guessed (unless you're using incr ID generator)!
Default configuration of CGI::Session uses Digest::MD5 to generate random, 32
character long identifier. Although this string cannot be guessed as easily by
others, if they find it out somehow, can they use this identifier against the
other person?
Consider the scenario, where you just give someone either via email or an
instant messaging a link to a Web site where you're currently logged in. The
URL you give to that person contains a session id as part of a query string.
If the site was initializing the session solely using query string parameter,
after clicking on that link that person now appears to that site as you, and
might have access to all of your private data instantly.
Even if you're solely using cookies as the session id transporters, it's not
that difficult to plant a cookie in the cookie file with the same id and trick
the web browser to send that particular session id to the server. So key for
security is to check if the person who's asking us to retrieve a session data
is indeed the person who initially created the session data.
One way to help with this is by also checking that the IP address that the
session is being used from is always same. However, this turns out not to be
practical in common cases because some large ISPs (such as AOL) use proxies
which cause each and every request from the same user to come from different
IP address.
If you have an application where you are sure your users' IPs are constant
during a session, you can consider enabling an option to make this check:
use CGI::Session '-ip_match';
For backwards compatibility, you can also achieve this by setting
$CGI::Session::IP_MATCH to a true value. This makes sure that before
initializing a previously stored session, it checks if the ip address stored
in the session matches the ip address of the user asking for that session. In
which case the library returns the session, otherwise it dies with a proper
error message.
LICENSING¶
For support and licensing see CGI::Session