http://www.perlmonks.org?node_id=200618

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

I will be running a cgi script on a server that will require basic authentication. As far as I can tell, my options are mangling the url, or with cookies. Well, i'm not going to throw a username and password in the url, so that leaves the use of cookies.

I have used this Thread in the past with CGI.pm, but, because of how often this page is going to be accessed I am to find an alternative method for setting the cookies and provide a proof of concept with and without cgi.pm being used.

My question to you is, how does one go about setting and retrieving cookies in perl without using cgi.pm? (telling my supervisor "you don't, use cgi.pm" isn't acceptable without code to back it up :-/ )

Replies are listed 'Best First'.
Re: Cookies without CGI.pm
by mattriff (Chaplain) on Sep 25, 2002 at 14:13 UTC
    Since you don't want to use CGI, I assume you don't want to use CGI::Cookie either? You'd need to set your own Set-Cookie headers, then. Some resources:

    RFC 2109
    Netscape's Spec

    I think you're probably better off just using a module, though.

    - Matt Riffle

Re: Cookies without CGI.pm
by spartacus9 (Beadle) on Sep 25, 2002 at 14:34 UTC
    Here is basically what the print statements would need to look like if you're going to do it yourself;
    Set-Cookie: cookie_name=cookie_value; expires=Wed, 11-Dec-2002 00:00:00 GMT; path=/; domain=abc.com;
    Use whatever means you want, but the end result will have to produce the above block, obviously substituting the cookie_name, cookie_value and other variables with your data. The RFC mentioned in the first reply is a good idea to read and I wholeheartedly agree -- if possible, use CGI.pm to accomplish this. It does all the work for you and it's harder to screw it up if you're using the module.
Re: Cookies without CGI.pm
by diotalevi (Canon) on Sep 25, 2002 at 15:23 UTC

    Incidentally you just said a Very Bad Thing. Normally your user passes in the authentication information and then you return a token - usually a hash of a server-side secret plus a session id. You then send over in the cookie both the hash and the session id. This provides for both uniqueness and security.

    The essentials are:

    server side secret: I generate a 2048 bit binary value and store it on the server. This is only used for input into the hash function. This must be kept secret (or regenerated) since this is your protection against forged session IDs. Keep in mind that you shouldn't just regenerate this willy nilly since you don't want to deplete your operating system's entropy pool. Normally I just create a secret.pl file which writes out another file Secret.pm with some binary value stored in a string sort of like  use constant SECRET => "\001sd3!@\007";.

    open RANDOM, RANDOM_DEVICE or die "Cannot open " . RANDOM_DEVICE . ": +$!"; $rc = read RANDOM, $secret, $bytes; close RANDOM or die "Cannot close " . RANDOM_DEVICE . ": $!"; die "Nothing was read!" if 0 == length $rc; die "Mismatched read: $bytes vs " . length($rc) . "!" if $bytes != len +gth $rc; # quote the binary value for inclusion in a double-quote string $secret =~ s/[\x00-\xff]/sprintf '\\%o', ord $&/gex;

    unique session id: Normally this just takes the form of an incrementing number ala 1 -> infinity (or wherever the number rolls over). This *must* be unique or you run the risk of colliding session keys. The best way of guaranteeing uniqueness is to just use a counter. Normally I just let my database handle this as a sequence. This is easy to attack so you augment it with a cryptographic hash.

    my $sessionid = $dbh->selectrow_array ( "SELECT nextval('UserSessionSeq')" );

    cryptographic hashing algorithm: Use MD5 or SHA1 (slower but more secure) to combine the sequence with the secret. Make sure you separate your two tokens to prevent them from running together. You can get collisions if you let them touch so just don't do that. I just separated the leading number from everything else by a single space. Easy.

    my $sessiondigest = md5_hex(sprintf("%u %s", $sessionid, Voter->SECRET +)

    The last trick is now you store both the session id and the session digest in the cookie you send to the client. Everytime the web browser requests something you have to check that the session id and session key match in your session records. You don't have to do anything complicated here - just a simple equality test will do.

    $dbh->selectrow_array ( "SELECT UserID, Activeuser, Created, Modified" . " FROM ValidSession" . " WHERE SessionID = ?" . " AND SessionDigest = ?", undef, $sessionid, $sessiondigest ); # if a row was returned then it was a good match otherwise something + is wrong (the session might have just expired as well - views are us +eful for that)

    There's an example of all this up at Voter @ greentechnologist.org.

Re: Cookies without CGI.pm
by Fastolfe (Vicar) on Sep 25, 2002 at 16:13 UTC
    I will be running a cgi script on a server that will require basic authentication

    Why not just use HTTP authentication? Generally re-inventing your own authentication system is unnecessary when HTTP has one built in.

    You might still want a cookie to maintain session state if you're anticipating that one user may log in from more than one location, but that's now a more manageable goal when authentication is removed from that requirement.

Re: Cookies without CGI.pm
by perrin (Chancellor) on Sep 25, 2002 at 18:51 UTC
    I just replaced CGI.pm with CGI_Lite in a script that only needed it to parse cookies, and it is much faster and smaller.