Overview
This document shows how to use Perl for managing state using two cookies.
Example Perl
################################################################################ # Code excerpts showing the use of two types of cookies to maintain an # authentication session that lasts for a specified time period or until the # browser is terminated, whichever comes first. # # User authentication is based on the presence of both cookies. Only if # neither cookie exists is CAS consulted for ticket validation (or issuance). # # The uid is contained in the 'persistent' cookie (the one with an expiry), # which is encrypted in a symmetric key (to prevent user tampering). # # Mike Friedman ################################################################################ $CAS_keyfile = "..." ; $CAS_cookie_expiry = "+10m" ; $CAS_cookie_secure = "1" ; chomp($thisprog = `basename $ 0 `); my $query = new CGI; $thishost = $query->virtual_host(); # Define the cookie domain as our full hostname: $CAS_thisDomain = $query->virtual_host(); # Define the cookie path: $CAS_thisPath = "/cgi-bin/" "$thisprog" ; # Create cookie name by removing file extension from App program name: ($CAS_cookieNamePersistent) = split(/\./,$thisprog); $CAS_cookieNameNonPersistent = "$CAS_cookieNamePersistent" "N" ; # Get the cookie encryption key: open(KEYFILE, "<$CAS_keyfile" ); chomp($CAS_key = ); close(KEYFILE); # Get the user's UID, either from an existing valid cookie, or else from CAS: $uid = &GetUIDFromCookie($CAS_cookieNamePersistent, "$CAS_key" ) if ( $query->cookie(-name=>$CAS_cookieNameNonPersistent) ); # If successful (i.e., both cookies were found), we won't send a new cookie: if ($uid =~ /^\d+$/) { $CAS_cookie_persistent = "" ; $CAS_cookie_non_persistent = "" ; } # Otherwise, check with CAS: else { $uid = &GetUIDFromCAS($action,$casService); # If successful, set a new cookie: if ($uid =~ /^\d+$/) { # Construct persistent cookie containing UID: $CAS_cookie_persistent = &ConstructSessionCookie($CAS_cookieNamePersistent,$uid,$CAS_key, $CAS_cookie_expiry,$CAS_thisDomain,$CAS_thisPath,$CAS_cookie_secure); # Construct non-persistent cookie: $CAS_cookie_non_persistent = $query->cookie( -name => "$CAS_cookieNameNonPersistent" , -value => "1" , -domain => "$CAS_thisDomain" , -path => "$CAS_thisPath" , -secure => "$CAS_cookie_secure" , ); # Set up cookie array: @CAS_cookies = ($CAS_cookie_persistent,$CAS_cookie_non_persistent); } } # Both cookies are included in the cgi header when the page is displayed, e.g., print $query->header(-cookie=>[ @CAS_cookies ]); ################################################################################ sub ConstructSessionCookie { # Build the contents of a cookie to send to the browser my ($name,$uid,$key,$expires,$domain,$path,$secure) = @_ ; my $query = new CGI; my $SessionID; my $cookie; # Make session ID out of encrypted UID: $SessionID = &EncryptString( "$key$uid" ,$key); # Construct the cookie: $cookie = $query->cookie( -name => "$name" , -value => "$SessionID" , -expires => "$expires" , -domain => "$domain" , -path => "$path" , -secure => "$secure" , ); return "$cookie" ; } ################################################################################ sub GetUIDFromCookie { # Retrieve the UID contained in a cookie originally sent from here my ($name,$key) = @_ ; my $query = new CGI; my $s; # SessionID in cookie my $u; # UID obtained from cookie my $qkey; # Meta-quoted $key for use in regexp # Retrieve the value of the 'sessionID' cookie: $s = $query->cookie(-name=>$name); return "" unless ($s); $s = &DecryptString( "$s" ,$key); # If the session ID is valid, then the UID immediately follows the key, # which is the first part of the value: $qkey = quotemeta $key; $u = "$1" if ($s =~ /^$qkey(.+)$/); return "$u" ; } ################################################################################ sub GetUIDFromCAS { # Return the UID as obtained from CAS, either by validating an existing # ticket, or by referring the user to CAS for a new ticket. In the latter # case , the UID will be obtained by validating the ticket that is returned # here by CAS. use strict; use LWP::UserAgent; use CGI; my ($thisService,$casService) = @_ ; my $query = new CGI; my $ua = LWP::UserAgent-> new ; my $casGet; my $casTicket; my $response; my $uid; # Retrieve the CAS ticket, if any, from the incoming URI: $casTicket = $query->url_param( 'ticket' ); # If there is a ticket, validate it with CAS, returning the user's UID: if ($casTicket) { # Construct the full URL of the CAS validation service: $casGet = "$casService\/serviceValidate?ticket=$casTicket&service=$thisService" ; # Retrieve the validation output from CAS: $response = $ua->get($casGet); # Peel off the UID from the response: if ($response->content =~ /(\d+)<\/cas:user>/) { $uid = "$1" ; } # If there's no uid, then retrieve the entire response (which should # include an error message) and return that to the caller instead of # a uid. else { $uid = "casTicket: $casTicket; " . $response->content; } } # If a valid ticket was not part of the query string, redirect the user's # browser to CAS to get a fresh ticket: else { print $query->redirect(-location=> "$casService/login?service=$thisService" ); } # Return the UID (or else the error response) obtained from CAS: return $uid; } ################################################################################ sub EncryptString { # Encrypt plaintext and return the Base64 encoding of the cyphertext use Crypt::CBC; use MIME::Base64; my ($plaintext,$key) = @_ ; my $CBCAlgorithm = "Blowfish" ; my $ciphertext; my $cipher; $cipher = Crypt::CBC-> new ( -key => $key , -cipher => $CBCAlgorithm , -salt => 1 , ); $ciphertext = encode_base64($cipher->encrypt($plaintext), "" ); return ($ciphertext); } ################################################################################ sub DecryptString { # Decode Base64 cyphertext and then decrypt it, returning the plaintext use Crypt::CBC; use MIME::Base64; my ($ciphertext,$key) = @_ ; my $CBCAlgorithm = "Blowfish" ; my $plaintext; my $cipher; $cipher = Crypt::CBC-> new ( -key => $key , -cipher => $CBCAlgorithm , -salt => 1 , ); $plaintext = $cipher->decrypt(decode_base64($ciphertext)); return ($plaintext); } |