thekestrel has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks
I'm having a bit of trouble getting the hang of CGI::Session and was hoping for some incite.

I'm trying to do authentication and session management, i.e. user authenticates with the usual user/pass and then session info is kept so that say you can say 'hello $user' on other pages.

Here is a bit of code hacked from the Members Area example in the cookbook.

#!/usr/bin/perl use strict; use warnings; use DBI; use CGI qw/:standard/; use CGI::Session; use Crypt::PasswdMD5; #Initiate the connection to the database my $dbh = DBI->connect("DBI:mysql:database=DATABASE;host=localhost", " +USER","PASS",{'RaiseError' => 1}); #Create the session #my $session = new CGI::Session("DBI:MySQL", undef, {Handle=>$dbh}); my $session = new CGI::Session(undef, undef, {Directory=>'/tmp'}); #create a CGI instance my $cgi = new CGI; #create a cookie with the session id my $cookie = $cgi->cookie(CGISESSID => $session->id); #$session->header(); sub init { my ($session, $cgi) = @_; if ( $session->param("~logged-in") ) { return 1; } my $trials = $session->param("~login-trials") || 0; my $name = $cgi->param('user') or return $session->param("~login-t +rials", ++$trials);; my $pass = $cgi->param('pass') or return $session->param("~login-t +rials", ++$trials);; if ( my $profile = _load_profile($name, $pass) ) { $session->param("~profile", $profile); $session->param("~logged-in", 1); $session->param("~login-trials",0); return 1; } return $session->param("~login-trials", ++$trials); } sub _load_profile { my ($user, $pass) = @_; local $/ = "\n"; my $query = "SELECT pass,email FROM users WHERE user = '$user'"; my $sth = $dbh->prepare($query); $sth->execute; my $dbpass; my $email; while ( my $ref = $sth->fetchrow_hashref() ) { $dbpass = $ref->{pass}; $email = $ref->{email}; } my @bits = split '\$', $dbpass; my $crypt = unix_md5_crypt($pass, $bits[2]); if ($crypt eq $dbpass) { my $mask = "x"; return {username=>$user, password=>$mask, email=>$email}; } return undef; } sub login_page { print "<br><b>Wrong information</b>\n"; open FILE, "<../login.html" or die "Could not open login file: $!\ +n"; while (<FILE>) { print $_; } close (FILE); } print header, start_html("Logging In..."); my $trials = init($session, $cgi); print "<br>trials = $trials\n"; if ( $session->param("~login-trials") >= 3 ) { print error("You failed 3 times in a row." . "Your session is blocked. Please contact us with" . "the details of your action"); exit(0); } unless ( $session->param("~logged-in") ) { print login_page($cgi, $session); exit(0); } my $profile = $session->param("~profile"); print "<br>Hello $profile->{username} ($profile->{email})"; print "<br><a href=\"home.cgi\">home</a>\n"; print end_html;


The code basically works... I changed the example so it uses a mysql database to authenticate with and that works. So you can use and valid user/pass and it works and if you use and invalid user/pass it doesn't let you in.

One thing doesn't work and one thing I'm not sure how you're supposed to do it.

The bit that doesn't work - The session counter doesn't increment and hence if you fail 3 times you don't get the fail screen. Looking at the /tmp when it keeps the session info it seems that for each refresh it creates a new session and doesn't use the existing one?? Why is this??

The bit I'm not sure about - I want to recall the session from another script, i.e. they click on a link and the next page knows who you are. When you click on the link the browswer would send back the cookie with the session id.
This is the bit I'm not sure about...
Do you basically capture the cookie and then create a new CGI::Session instance with that info so it know which file to look up in /tmp to see if you're logged in... or is it supposed to do all of that automatically?
UPDATE: This bit I've worked out, in the first bit of source I created the cookie, but I never printed it out so that it made it to the client.

Here is where i was heading, but it doesn't work...

#!/usr/bin/perl use strict; use warnings; use CGI qw/:standard/; use CGI::Cookie; use CGI::Session; my %cookies = fetch CGI::Cookie; print header, start_html; print "<br> Booo\n"; print "<br>Cookies:\n"; my $session_vars; foreach my $key (keys %cookies) { $session_vars = $cookies{$key} if ( $key eq 'CGISESSID' ); } my @vars = split ';', $session_vars; my $session_id; foreach my $v ( @vars ) { if ( $v =~ /CGISESSID/ ) { my @bits = split '=', $v; $session_id = $bits[1]; } } print "<br>sess : $session_id\n"; my $session = new CGI::Session(undef, $session_id, {Directory=>'/tmp'} +); my $profile = $session->param("~profile"); print "<br> Hello: $profile->{username}\n"; print end_html;


Update: The second script gets completely gutted now, instead of capturing the cookies youself, you just create a cgi instance and pass it as the second argument in the new CGI::Session initialization and it goes and finds the olds session itself if it exists.

Update2: I have it all working now, the main problem was that I was creating a new session everytime I refreshed the page (or failed a login) which created a new session ID and hence never made the counter more than 1. Below is a snippet that shows what to do...i.e. include the $cgi object in the session call. It uses the CGI to object to see if it has an associated session and if not it makes one.
#Initiate the connection to the database my $dbh = DBI->connect("DBI:mysql:database=DATABASE;host=localhost", " +USER","PASS",{'RaiseError' => 1}); #create a CGI instance my $cgi = new CGI; #Create the session #my $session = new CGI::Session("DBI:MySQL", undef, {Handle=>$dbh}); my $session = new CGI::Session(undef, $cgi, {Directory=>'/tmp'});

Can anyone offer any insight into why the counter does not work for logins??

Regards Paul

READMORE tags added by Arunbear; also changed title from 'CGI::Session'

Replies are listed 'Best First'.
Re: Using CGI::Session for authentication
by rlucas (Scribe) on Jun 24, 2005 at 00:30 UTC
    You need to 1. make sure that C::S is using the session ID to pull the previous session for a given user, and 2. make sure that the session ID is getting output to the user.

    You can do 1. in two ways:

    • Pass your CGI instance to C::S in the constructor. It'll do its best to look for a cookie or param with the proper name.
    • You can pull the cookie or param from CGI with the session ID and pass that in.
    As far as 2., your best bet is to always, no matter what, print your header as including a cookie containing the CGISESSID (or whatever you've decided to name it).

    Of course, this will bomb miserably for users sans cookies. Be aware of this and consider testing for cookie usage and/or appending the ID to URLs and form variables (a PITA IMO).

      rlucas i am too working with the same CGI::Session but i find myself no where as far as you say that
      your best bet is to always, no matter what, print your header as including a cookie containing the CGISESSID (or whatever you've decided to name it).
      i have reached sucess still this point i am able to print the cookie which i am sending to the user
      but the problem is that on subsequest pages when i try running the same script it creates a new session rather than intiallizing the existing one . i would like to add that i am not able to fetch the cookie
      i dont know whether my understanding is wrong or my handling of the code is wrong .
      my code is almost similar to the one posted with this node kidly guide

        You need to find out exactly what is happening. I recommend that you begin a fresh session with cookies cleared from browser and browser set to ask you for each cookie. Also, use file-based Data::Dumper serialization (for transparency in observing file contents) in CGI::Session and clear out the session directory. (Clean initial condition) Finally, make sure to write the received cookie info to the error log so that you can tell when your app either sets (via browser asking you) or receives (via server error log) a cookie.

        Then, go to the app and observe the cookie that is set and the file that is created. Go again to the app, verify that the cookie that was set is indeed sent back, and processed properly. Verify that the same cookie gets set again with identical session ID value.

        The answer for you will be found wherever this breaks down. Good luck.

      rlucas,
      Thanks for your advice, I have it all working now with the cookie method. I haven't implemented the idea of putting the session ID in the URL yet if cookies aren't enabled, but its on the list. =)

      Regards Paul.

        Kestrel, your example calls a file called login.html. I am kind of a rookie and it would be very helpful if you could add that file to the post.