8.11.3. Discussion
When using cookie authentication,
you have to display your own login form:
<form method="post" action="login.php">
Username: <input type="text" name="username"> <br>
Password: <input type="password" name="password"> <br>
<input type="submit" value="Log In">
</form>
You can use the same pc_validate(
)
function from the Recipe 8.10 to verify the
username and password. The only difference is that you pass it
$_REQUEST['username'] and
$_REQUEST['password'] as the credentials instead
of $_SERVER['PHP_AUTH_USER'] and
$_SERVER['PHP_AUTH_PW']. If the password checks
out, send back a cookie that contains a username and a hash of the
username, and a secret word. The hash prevents a user from faking a
login just by sending a cookie with a username in it.
Once the user has logged in, a page just needs to verify that a valid
login cookie was sent in order to do special things for that
logged-in user:
unset($username);
if ($_COOKIE['login']) {
list($c_username,$cookie_hash) = split(',',$_COOKIE['login']);
if (md5($c_username.$secret_word) == $cookie_hash) {
$username = $c_username;
} else {
print "You have sent a bad cookie.";
}
}
if ($username) {
print "Welcome, $username.";
} else {
print "Welcome, anonymous user.";
}
If you use the built-in session support, you can add the username and
hash to the session and avoid sending a separate cookie. When someone
logs in, set an additional variable in the session instead of sending
a cookie:
if (pc_validate($_REQUEST['username'],$_REQUEST['password'])) {
$_SESSION['login'] =
$_REQUEST['username'].','.md5($_REQUEST['username'].$secret_word));
}
The verification code is almost the same; it just uses
$_SESSION instead of $_COOKIE:
unset($username);
if ($_SESSION['login']) {
list($c_username,$cookie_hash) = explode(',',$_SESSION['login']);
if (md5($c_username.$secret_word) == $cookie_hash) {
$username = $c_username;
} else {
print "You have tampered with your session.";
}
}
Using cookie or session
authentication instead of HTTP Basic authentication makes it much
easier for users to log out: you just delete their login cookie or
remove the login variable from their session. Another advantage of
storing authentication information in a session is that you can link
users' browsing activities while logged in to their
browsing activities before they log in or after they log out. With
HTTP Basic authentication, you have no way of tying the requests with
a username to the requests that the same user made before they
supplied a username. Looking for requests from the same IP address is
error-prone, especially if the user is behind a firewall or proxy
server. If you are using sessions, you can modify the login procedure
to log the connection between session ID and username:
if (pc_validate($_REQUEST['username'],$_REQUEST['password'])) {
$_SESSION['login'] =
$_REQUEST['username'].','.md5($_REQUEST['username'].$secret_word));
error_log('Session id '.session_id().' log in as '.$_REQUEST['username']);
}
This example writes a message to the error log, but it could just as
easily record the information in a database that you could use in
your analysis of site usage and traffic.
One danger of using session IDs is that sessions are hijackable. If
Alice guesses Bob's session ID, she can masquerade
as Bob to the web server. The session module has two optional
configuration directives that help you make session IDs harder to
guess. The
session.entropy_file directive contains a
path to a device or file that generates randomness, such as
/dev/random or
/dev/urandom. The
session.entropy_length directive holds the
number of bytes to be read from the entropy file when creating
session IDs.
No matter how hard session IDs are to guess, they can also be stolen
if they are sent in clear text between your server and a
user's browser. HTTP Basic authentication also has
this problem. Use SSL to guard against network sniffing, as described
in Recipe 14.11.