Using cookies to log users

We had a CGI script running on Apache on an HP-UX 11.0 box. The access_log file for apache showed each time a user POST'd via the app, but the log only showed the user's IP address, not name. Management wanted to know who the users were so that they could reward the heavier users as a means to get people to use the app more (and abandon the old manual method).

The first approach was to try to incorporate mod_auth to authenticate users. Unfortunately, if I used mod_auth pointed at just a file, there was no way to have wildcards in the file to allow the users to use any ID to get in. Remember, I don't want to prevent anyone from getting to the app, I just want to know who is using it. Using mod_auth_ldap didn't work any better. Despite many attempts to get a test Fedora Core 3 box running Apache to talk to the company's Active Directory server, I could never get the LDAP authentication to work. On the HP box it was even worse: the child process would die whenever the LDAP module was invoked.

But I didn't really need the authentication that mod_auth provides, I just needed a way to identify the users. So, I came up with the idea of using a cookie to store the value of the user's login ID. I added this to the CGI script that the users wanted to run:

unless ($cookie = cookie('loginid')) { require "/opt/hpws/apache/cgi-bin/cookiecheck.cgi"; } else { # provide app to user This checks to see if the user's browser included the loginid cookie in the request for the CGI script. If it didn't, it calls the cookiecheck.cgi script, which is just a form that asks the user for an ID. It looks like this:

#!/usr/bin/perl -w # cookiecheck.cgi - form for getting a login ID from user use CGI qw(:standard); print header(); print start_html("CookieMonster"); print <<EndOfHTML; <FORM ACTION="/cgi-bin/setloginidcookie.cgi" name="form" METHOD="POST"> <body bgcolor="#FFCF10" topmargin="0" leftmargin="0" onload="document.form.loginID.focus()"> &nbsp;<p> <h4>&nbsp;&nbsp;Please enter your login ID. This is the same as your network login. A password is not necessary.</h4><p> &nbsp;&nbsp;<input type="text" name="loginID" tabindex="1"> <input type="submit" value="Enter" tabindex="2"> </form> <script type="text/javascript"> </script> EndOfHTML print end_html;

cookiecheck.cgi then calls setlogincookie.cgi which takes the value assigned to loginID and places it in a cookie and sends it to the browser, like so:

#!/usr/bin/perl -w # setlogincookie.cgi - gets loginid from cookiecheck.cgi, puts that value into # a cookie for the user, redirect user back to petredashboard.cgi scipt which is # what the user originall wanted use CGI qw(:cgi-lib); use CGI qw(:standard); use CGI qw(:cgi); %formdata = Vars; my $q = CGI->new(); if ($formdata{loginID}) { my $cookie = $q->cookie(-name=>'loginid', -value=>$formdata{loginID}, -expires=>'+1000d'); print $q->redirect( -url => 'http://porrep/cgi-bin/petredashboard.cgi', -cookie => $cookie ); print redirect("http://porrep/cgi-bin/petredashboard.cgi"); } else { print $q->redirect("http://porrep/cgi-bin/petredashboard.cgi"); }

Then setlogincookie.cgi redirects the user back to the originally desired CGI script, and the whole process starts over again. But this time, the user will have a cookie and will therefore just be passed the contents of the desired part of the CGI script. This approach also has the advantage or preventing the user from just hitting enter without providing a login name to try to get to the app. Just hitting enter at the form will assign no value to the loginid variable and so will fail. I set the cookie to expire after 1000 days so that they only have to go through this process once (at least for a long time).

One other thing: You also need to tell Apache to log the cookies. I used the following CustomLog setting in the httpd.conf file to put the cookie entries into a separate log file to make it easier to analyze:

CustomLog logs/cookies "%{loginid}C %h %t %r" The above says to log the value of the loginid cookie, followed by the client host address, the date/time, and the document requested.

I used this document to figure out a way to both set a cookie in the header and redirect the user back to the originally-desired CGI script. This was important, because most docs I found on redirects said NOT to send a header; but if I don't send a header then the cookie wouldn't get set. I'll show the code from that doc just in case it disappears in a few years.

#! /usr/bin/perl -w use CGI qw(:cgi); my $q = CGI->new(); my $cookie = $q->cookie( -name => 'yummy_cookie', -value => 'chocolate chip', -domain => '', -expires => '+10d', -path => '/' ); print $q->redirect( -url => '', -cookie => $cookie ); __END__

See also the CGI 101 Book, in particular chapters 10 & 17.