Perl CGI persistent cookies - perl

I want the user to non have to login even if the browser was closed. My cookies are set to expire after a month.
when the user logs in sucessfully
$session = CGI::Session->new (undef, undef, {Directory=>'tmp/'})
or die CGI::Session->errstr;
$session->param('username', $username);
$session->expire('+1M');
$cookie = $cgi->cookie( -name=>$session->name, -value=>$session->id );
print $cgi->header(-cookie=>$cookie );
They are then redirected to another page that they can access as long as they don't close the browser. This is the code in the second page:
my $cookie = $cgi->cookie('CGISESSID');
if ($cookie){
print $cgi->header(-cookie => $cookie);
else{
//ask them to relog in
}
I can see the sessions created in tmp/. How do I load an existing cookie after the browser is closed. How do I know which session to load based on the user/browser?

As long as you set a future expiration date on your cookies, they should persist even after a user restarts their browser (as long as they restart before that date, of course). To load the cookie, do exactly what you're doing:
my $cookie = $cgi->cookie('CGISESSID');
To try to load an existing session using the cookie you can simply pass your CGI object to the new method of CGI::Session:
my $session = new CGI::Session(undef, $cgi, {Directory=>"/tmp"});
This will attempt to initialize an existing session using the cookie passed in with the CGI request; if one doesn't exist, it will create a new session. Note that this assumes the cookie name is CGISESSID. To use another name, run:
CGI::Session->name("MY_SID");
# or
$session->name("MY_SID");
$session = new CGI::Session(undef, $cgi, {Directory=>'/tmp'});
If you haven't already, I would recommend reading through the CGI::Session tutorial.
EDIT: The session was set to expire in one month
$session->expire('+1M');
but the cookie was not. If you don't set an expiration on a cookie, the browser will store it in memory but not on disk; as soon as you close the browser, the cookie disappears. To set the cookie expiration, do something like
$cookie = $cgi->cookie( -name=>$session->name, -value=>$session->id, -expires=>'+1M' );

Related

How to delete a session in cgi-perl?

I have a perl-cgi script through which I am trying to log in.
When the UserName and password are valid, I create a session and redirect a cookie to another page.
However, after the session expires(I have set the expiration time), I do not see it get deleted from the /tmp/sessions folder in this case. I have used the command to delete the session as well.
Can someone help me to delete the session once it expires? Also, does the cookie expire once the session is deleted?
use CGI::Session;
use CGI::Session::Tutorial;
use CGI::Session::Driver::file;
use CGI::Cookie;
my $session = new CGI::Session("driver:File", undef, {Directory=>"/tmp/sessions"});
my $sid = $session->id();
#my $cookie = $query->cookie(CGISESSID => $session->id);
my $cookie = $query->cookie(-name=>"CGISESSID",
-value=>$session->id,
-domain=>'abc.com',
-expires=>"+5m",
-path=>"/");
print $query->redirect(-uri => 'http://abc.cgi', -cookie => $cookie);
$session->param("UserName", $loginUserName);
$query->hidden( 'UserName', $loginUserName );
$session->expire("UserName",'1m');
$session->expire('+5m');
$session->delete();
To avoid confusion with ->delete, I'm going to use the word "remove" instead of "delete" to refer to the removal of the session from storage.
Can someone help me to delete the session once it expires?
The removal doesn't happen when the session expires. That would require having a continually running process. Furthermore, at no point does CGI::Session scan storage for expired sessions; that would take too long since it would require loading each and every session. Instead, CGI::Session only removes expired sessions when you try to load them.
#!/usr/bin/perl
use strict;
use warnings qw( all );
use feature qw( say );
use CGI::Session qw( );
use CGI::Session::Driver::file qw( );
my $session_id; # This represents the browser's cookie.
# These represent requests made the by the browser.
for my $request_num (1..3) {
my $session = CGI::Session->new("driver:file", $session_id, { Directory => "/tmp/sessions" });
$session->expire("1s");
$session_id = $session->id; # This represents setting the browser's cookie.
say "$request_num: ", $session->id;
say "$request_num: ", $session->param("foo") // "[undef]";
$session->param("foo" => "bar");
# This represents time passing by before the third request.
if ($request_num == 2) {
say "Letting session expire...";
sleep(2);
}
}
Output:
$ ./a
1: c57ab28952c6ed422c15f1a223f4b45d
1: [undef]
2: c57ab28952c6ed422c15f1a223f4b45d
2: bar
Letting session expire...
3: df8ba3b66f23a9a2a652520fa6b4c30b
3: [undef]
$ ls -1 /tmp/sessions
cgisess_df8ba3b66f23a9a2a652520fa6b4c30b
If you want to prevent files from accumulating on your drive, create a cron job that deletes old files.
find /tmp/sessions -mindepth 1 -maxdepth 1 -mtime 7 -delete
Also, does the cookie expire once the session is deleted?
No, the cookie expires when you tell it to expire. The thing is, it doesn't matter if the browser's cookie expires or not. For the second argument of new, there's no difference between passing undef, passing the id of a deleted session and passing the id of an expired session; you'll get a new session in all three cases. If anything, it's actually better if it doesn't expire as soon as the session expires because this allows the session to be removed (as demonstrated above).
How to delete a session in cgi-perl?
$session->delete is indeed the way to go, but the actual removal only happens when you would save (flush) the session.
$session->delete();
$session->flush(); # Or let `$session` get destroyed.
As the documentation notes
delete()
Sets the objects status to be "deleted". Subsequent read/write requests on the same object will fail. To physically delete it from the data store you need to call flush(). CGI::Session attempts to do this automatically when the object is being destroyed (usually as the script exits), but see "A Warning about Auto-flushing". (emphases mine)
You go on to ask:
Also, does the cookie expire once the session is deleted?
Of course not. You already sent a cookie to the user's browser with an expiration time of five minutes in the future. The cookie will expire then.
If, in the mean time, you have forced the expiration of the session on the server, the user's browser will still send the previously received cookie. Your application will just not find a session corresponding to the session identifier stored in the cookie.
You really need to understand the HTTP request/response cycle before taking one more step.
Per the CGI::Session documentation, deleteing a session "Sets the objects status to be "deleted". Subsequent read/write requests on the same object will fail. To physically delete it from the data store you need to call flush()." (emphasis mine)
Also, per the CGI::Session::Tutorial, "Expiring a session is the same as deleting it via delete(), but deletion takes place automatically." It is not necessary (or useful) to delete a session after it has expired.

How do I change the session id with CGI::Session?

I have this application where you create a session cookie like so:
$session = CGI::Session->new() or die CGI::Session->errstr;
$cookie = CGI::Cookie->new(-name=>$session->name, -value=>$session->id,-expires=>'+2h', -secure => 1 );
And then set the header like this:
print $q->header(-cookie=>$cookie);
I need to change the session ID of this cookie upon logging in to the application (in a smiliar manner to php's session_regenerate_id ). Is there anyway of doing this in Perl? I've been looking through the documentation and I can't find any ways of doing this really. If not, other suggestions on how to solve this are welcomed.

CGI::Session Randomly clearing parameter

When users log in I have created a new Session in Perl CGI, and stored the session ID in a cookie (CGISESSID). This cookie has then been sent the next page via a redirect, as shown below:
my $session = CGI::Session->new("driver:File", undef, {Directory=>"/tmp"});
my $sid = $session->id();
$session->param("username", $username);
$session->expire('+15m');
print redirect ( -cookie => cookie(CGISESSID => $session->id), -uri => 'x.cgi');
On 'x.cgi' (and all other pages of the site) I have:
my $sid = cookie ('CGISESSID') || param('CGISESSID') || undef;
my $session = CGI::Session->load(undef, $sid, {Directory=>'/tmp'});
$session->expire('+15m');
my $username = $session->param("username");
if (!defined ($username)) {
print redirect ("login.cgi");
}
However, after a random amount of time/clicks (well before the 15m mark, anywhere from the 1st click to a click 2-3 minutes later), it redirects to login.cgi.
Debugging has shown me that although it redirects me (and $username is not defined), the code still obtains the correct session ID originally created (the session seems to be still intact), and the cookie remains.
I don't pass CGISESSID as a parameter on any page requests/links (as I assume a cookie saves me from doing this)
Any idea what could be causing $username to be undefined after a random amount of time?
Given that your description is correct and your code works for some time, and after that stops working, I would suggest checking that noone is cleaning up your /tmp directory and, in particular, the session file there.
When you create a session using CGI::Session module with file driver it just creates a text file in the given directory (/tmp in your code). It's a text file with some Perl code, you can cat it and see what's inside:
$ cat cgisess_126b3cd2c4b9ac6eaac0185afbc46d34 && echo
$D = {'_SESSION_ID' => '126b3cd2c4b9ac6eaac0185afbc46d34','_SESSION_ATIME' => 1413493418,'_SESSION_REMOTE_ADDR' => '','_SESSION_CTIME' => 1413493418};;$D
For file driver the filename it uses for the session can be obtained by
my $filename = sprintf $CGI::Session::Driver::file::FileName, $session->id;
Check that it is created and exists for at least 15 minutes. If it disappears, blame some cron job, some other script or your hosting provider.

Mojolicious session expires versus default_expiration

I have a program where I want the session data to expire at an absolute Epoch time after a request. I don't want the expiration to update for every request.
default_expiration works but not expires.
This does not work:
post '/access' => sub {
my $self = shift;
my $user = $self->param('username');
if ($self->authenticate($user, $self->param('password'))) {
### Set this otherwise timeout refreshes for every request...
$self->session(expires => time + 120);
(...)
}
};
Mojolicious cookie set to 1 hour (3600s) the default...
$self->sessions->default_expiration(120) works but reset for every browser request.
I am using the Mojolicious::Plugin::Authentication plugin.
You can use the expiration key to set the same value as the default_expiration does. Before you set the value though, be sure to check to see if the key exists already and don't overwrite it if it does.

Perl, CGI::Session doesn't work correctly

I'm writing a web application that needs to make use of module CGI::Session ver 4.35. Upon receiving request from the client with an SESSIONID string
$sid = $cgi->cookie("CGISESSID") || $cgi->param("CGISESSID") || undef;
it tries to recreate the session by passing the $sid as an argument
$session = new CGI::Session($sid) or ($logger->error(CGI::Session->errstr) and die);
If there was an session created with that sid, $session->id and $sid are suppose to be the same, but the truth is it's NOT.
This is the statement where I create a completely new session
$session = new CGI::Session("id:md5", undef, {Directory=>$SESSION_DIR})
or ($logger->error(CGI::Session->errstr) and die);
What went wrong here? How am I supposed to use the module CGI::Session correctly?
I'm the maintainer of CGI::Session. I recommend creating the session the same way in all cases, like this:
$session = CGI::Session->new("id:md5", $cgi, {Directory=>$SESSION_DIR});
This follows the recommended syntax in the docs for new(). I also recommend making sure that you call flush() explicitly near the end of the script. The reason for that is explained more here:
http://metacpan.org/pod/CGI::Session#A-Warning-about-Auto-flushing
there really is no need for you to grab the session cookie yourself. If you pass a CGI object instance to CGI::Session it does it for you. So, basically, the above code by jfd can be re-written like this:
my $session = CGI::Session->new( $query );
$self->header_props(-cookie => $session->cookie);
And $query->cookie() and if/else blocks are all redundant, because they already exist in CGI::Session's logic!
So the above code checks for client's cookie named CGI::Session->name (which defaults to CGISESSID). If it doesn't exist, looks for query parameter in the URL or request's body named CGI::Session->name (which also defaults to CGISESSID). If it can get claimed session id it tries to load its data into the session. If the session id cannot be validated (either expired, or forged) it ignores it, and creates a brand new, empty session.
If the session id cannot be found in either cookie nor in URL parameters it creates a new session.
most examples of session management I see out there try to re-invent the session logic inside the code while using CGI::Session. I'm just here to tell you that all that code is completely redundant!!!
Enjoy using CGI::Session!
So, I'm not clear why you are creating the session twice? You want to first try and get the sid, and then create the session with it, whether it exists or not. If it doesn't exist, set the cookie. It's been awhile, but I pulled this from an old piece of code...
my $sid = $query->cookie('CGISESSID') || undef;
# grab the session obj, if one already exists otherwise create one
my $session = new CGI::Session( "id:md5", $sid, { Directory => $SESSION_DIR } );
# If there is no user cookie, or it's non existent, we give them a new one
if ( !$sid or $sid ne $session->id ) {
my $cookie = $query->cookie(
-name => 'CGISESSID',
-value => $session->id,
-expires => EXPIRE_TIME
);
$self->header_props( -cookie => $cookie );
}