Gossamer Forum
Home : Products : DBMan : Customization :

cookie question

Quote Reply
cookie question
Embarking on finally letting users store login/pwd info in a (persistent) cookie, I've got one question: It seems that Eli Finkelman's cookie mod - the one that uses Javascript - would set the cookie ALSO on login failures. For the cookie is set before the form is submitted, and the javascript only performs formal validity checks (are both fields filled in), but can of course not check the dbman password. This would be one reason for me not to use it.

I also don't want to use anything which works with Matt's script library. This might be wrong, but I've been warned that Matt's scripts are based on Perl 4, that they are on the whole rather insecure and should therefore be avoided.

So my question is whether anyone has already written yet another login cookie routine, preferably one that uses CGI.pm or CGI::Cookies.
kellner
Quote Reply
Re: [kellner] cookie question In reply to
... figured it out, wasn't too difficult reading the CGI.pm documentation.

In case anyone's interested, here's what I did:

When a user calls the database without passing a "uid" value, the script first checks whether there's a cookie set, with the username and the (encrypted) password. If not, the user is taken to the login page. If yes, the cookie is read in and the user is logged on automatically (which also requires writing a session ID).

On the login page, the user can check whether they want to login automatically the same time. If so, a cookie is written with username and (encrypted) password.

All of this requires adding only one line of code in html.pl, about ten lines in db.cgi and another ten in auth.pl. I'm really amazed at how simple this is with the CGI module!

If anyone's interested, I'll post the code.
kellner
Quote Reply
Re: [kellner] cookie question In reply to
Please do post the code, and I'll be sure to add it to the FAQ.

I'm sure there will be several interested in an alternative.

Thanks

Unoffical DBMan FAQ

http://creativecomputingweb.com/dbman/index.shtml/
Quote Reply
Re: [LoisC] cookie question In reply to
OK, here we go.

This mod requires that the module CGI.pm is installed on the server. This is highly likely, because CGI.pm is part of the standard perl distribution since perl 5.004.

This mod offers users the possibility to check, on the login form, whether they want to log on automatically next time they call the script. When the box is checked, the script creates a cookie after username and password have been validated.

Every time someone calls the script, it checks whether a session ID is passed to it. If that's the case - fine, the user is already logged on. This is how dbman as such works.
Now here's the change: If there's no session ID passed to the script, the mod checks whether there's a cookie. If so, the cookie is read and a session ID is created based on the username stored in it. This means that the user is logged on automatically (user rights are assigned at a later stage in the script.) If there is no cookie and no session ID, the mod does nothing, and dbman sends the user to the login form.

NOTE: In the code given below, replace the two uses of COOKIENAME by whatever you want to call the cookie (e.g. "dbmankellner"), and replace YOURDOMAIN in the code for auth.pl with the URL of your domain (e.g. "www.yourdomain.com"). The URL must have at least two periods in it to be properly identifiable as a URL. The expiry date is set to three months here. You can change this as you like: "+1h" means one hour from now, "+10d" ten days, "+1M" one month, "+1Y" one year, and so forth.

Here's the actual mod:

1) In html.pl, sub html_login_form, add this hidden input field to the login form:

<input type="checkbox" name="rememberme"> log on automatically next time (requires cookie)


2) Add this line to the beginning of auth.pl:

use CGI qw(:standard);

3) In auth.pl, replace the subroutine auth_check_password with this code:

sub auth_check_password {
# --------------------------------------------------------
# This routine checks to see if the password and userid found
# in %in (must be 'pw' and 'userid') match a valid password and
# userid in the password file.
# It returns a status message and a userid which is built by a
# "user name" + "random number"
# which get's stored in the query string.

my ($pass, @passwd, $userid, $pw, @permissions, $file, $uid);
my ($server_auth) = $ENV{'REMOTE_USER'} || $ENV{'AUTH_USER'};

if ($auth_no_authentication || (($db_uid eq 'default') && $auth_allow_default)) {
return ('ok', 'default', @auth_default_permissions);
}
elsif ($server_auth) { # The user has logged in via server authentication.
return ('ok', $server_auth, &auth_check_permissions($server_auth));
}
elsif ($in{'login'}) { # The user is trying to login.
open (PASSWD, "<$auth_pw_file") || &cgierr("unable to open password file. Reason: $!\n");
@passwds = <PASSWD>; # Let's get the user id and passwords..
close PASSWD;
my ($view, $add, $mod, $del, $admin);
PASS: foreach $pass (@passwds) { # Go through each pass and see if we match..
next PASS if ($pass =~ /^$/); # Skip blank lines.
next PASS if ($pass =~ /^#/); # Skip Comment lines.
chomp ($pass);
($userid, $pw, $view, $add, $del, $mod, $admin) = split (/:/, $pass);
if (($in{'userid'} eq $userid) && (crypt($in{'pw'}, $pw) eq $pw)) {
srand( time() ^ ($$ + ($$ << 15)) ); # Seed Random Number
$db_uid = "$userid." . time() . (int(rand(100000)) + 1);# Build User Id
open(AUTH, ">$auth_dir/$db_uid") or &cgierr("unable to open auth file: $auth_dir/$uid. Reason: $!\n");
print AUTH "$uid: $ENV{'REMOTE_HOST'}\n";
close AUTH;
foreach (0 .. 3) { $permissions[$_] = int($permissions[$_]); }
&auth_logging('logged on', $userid) if ($auth_logging);
## begin cookie mod ############
if ($in{'rememberme'}) {
my $q = new CGI;
my %cookval;
$cookval{'username'} = "$userid";
$cookval{'password'} = crypt($in{'pw'}, $pw);
my $cookie_out = $q->cookie(-name=>"COOKIENAME",
-value=> \%cookval,
-domain => "YOURDOMAIN",
-expires=>'+3M', # cookie expires after three months - adjust as you please
-path=>'/',
);
print $q->header(-cookie=>$cookie_out);
$html_headers_printed = 1;
}
# end cookie mod ##############
return ('ok', $db_uid, $view, $add, $del, $mod, $admin);
}
}
return ("invalid username/password");
}
elsif ($db_uid) { # The user already has a user id given by the program.
(-e "$auth_dir/$db_uid") ?
return ('ok', $db_uid, &auth_check_permissions($db_uid)) :
return ('invalid/expired user session');
}
else { # User has not logged on yet.
return 'no login';
}
}

4) Change the beginning of db.cgi to this code:

#!/usr/bin/perl

use CGI qw(:standard);

$db_script_path = ".";

# Load the form information and set the config file and userid.
local(%in) = &parse_form;
$in{'db'} ? ($db_setup = $in{'db'}) : ($db_setup = 'default');

# Required Librariers
# --------------------------------------------------------
# Make sure we are using perl 5.003, load the config file, and load the auth file.
eval {
unshift (@INC, $db_script_path);
require 5.003; # We need at least Perl 5.003
unless ($db_setup =~ /^[A-Za-z0-9]+$/) { die "Invalid config file name: $db_setup"; }
require "$db_setup.cfg"; # Database Definition File
require "auth.pl"; # Authorization Routines
};
if ($@) { &cgierr ("Error loading required libraries.\nCheck that they exist, permissions are set correctly and that they compile.\nReason: $@"); }

# If we are using benchmarking, then we start a timer and stop it around &main. Then we print the difference.
if ($db_benchmark) { $t0 = new Benchmark; }

################ begin cookie mod
if ($in{'uid'}) { $db_uid = $in{'uid'};} # if the user is logged on already: set $db_uid to the value of parameter "uid"
else { # if user is not logged on: check whether there's a cookie
my $q = new CGI;
my %cookval = $q->cookie("COOKIENAME");
my $userid = $cookval{'username'};
my $password = $cookval{'password'};
if ($userid) { # if there's a cookie, create a session ID using the username from the cookie
srand( time() ^ ($$ + ($$ << 15)) ); # Seed Random Number
$db_uid = "$userid." . time() . (int(rand(100000)) + 1);# Build User Id
open(AUTH, ">$auth_dir/$db_uid") or &cgierr("unable to open auth file: $auth_dir/$db_uid. Reason: $!\n");
print AUTH "$db_uid: $ENV{'REMOTE_HOST'}\n";

close AUTH;
}
else { # if there's no cookie - no session ID. Script will take user to login form later.
$db_uid = "";}
}
######################## end cookie mod

eval { &main; };


One thing: the password is printed to the cookie (encrypted). I'm not sure whether it is necessary to store the password to begin with, as we're not really doing anything with it. It would be possible to authenticate the user again after the cookie is read, just to make sure, but I guess that wouldn't be necessary. For if a deleted user is automatically logged on, the script will then proceed to assign the appropriate rights to them on the basis of the password file. Alas, it won't find the user, and the user doesn't get any rights. At this point the script would return an error, so there's no danger that deleted users still mess with your database because they have a cookie stored on their computer.

Further suggestions for improving the code would be greatly appreciated.
kellner
Quote Reply
Re: [kellner] cookie question In reply to
This is great (havent seen this thread untill now...). Im working on doing something similar. One question I have: what about security in this case? How secure is it to store the users ID in a cookie and rely on that being correct when using it in the auth process? eg would it be possible for someone to create an identical cookie with whatever username stored in it to access others accounts?

Im probably missing something here, pls advice. Also, Kellner, have you made any additions since this post?

Thanks a lot! E
Quote Reply
Re: [eric74] cookie question In reply to
I love this mod, but would love it even more if it worked 100%.

What I keep having is that a lot of times, a cookie isn't written. I don't know why, and than suddenly, from the same page, it is...

It's happened on more domains. So I checked if I could find clues.

First I made sure the cookie's domain value was with two dots:
-domain => ".mydomain.com",

than I made sure (read it somewhere on this forum) the path was not just the '/' but now looks like this:
-path=>'/cgi-bin',

But it all doesn't help. It seems it works very much at random. Has anybody worked on this problem? Or found a completely different solution?

Thanks,
Lex
Quote Reply
Re: [Lex] cookie question In reply to
By the way Kellner,

nms has a cookie library to replace matt's.

quote:
nms is a project that was started by the London Perl Mongers. Its goal is to provide drop-in replacements for the scripts found at Matt's Script Archive. These scripts have been developed from the ground up to emulate the behavior of Matt's free Perl scripts, with the caveat that you must run Perl 5.004_04 or later. Almost everyone does.

Matt's Script Archive does not provide support for or guarantee the quality of nms scripts. We are pleased to offer our users the choice of which version they would like to download, and in many cases the nms code is better written and more secure. Their code is released under the GPL and Artistic License allowing us to offer it here.
/quote

you can find it at:
http://www.scriptarchive.com/nms.html

have fun,
Lex