Professional PHP Programming - User Identification and Authentication (
Page 5 of 9 )
Every once in a
while you will need to uniquely identify a user. Users are usually identified by
a challenge and response system. A username/password combination is a good
example of such a system, with the challenge being ‘Give me the secret password
for Alice’ and the response being Alice’s secret password. This works because
user Alice should be the only one who knows the secret password.
User
Authentication by the Web ServerThere is a standard way to authenticate
users that requires minimal effort on the side of the PHP programmer. You can
simply let Apache take care of authenticating the users.
AuthName "Secret page" # The realm
AuthType Basic
# The password file has been placed outside the web tree
AuthUserFile /home/car2001/website.pw
<LIMIT GET POST>
require valid-user
</LIMIT>
You need to put these directives in a file called .htaccess
in the directory you are trying to protect. You also need to create a file with
the username/password combinations. You can do this with the htpasswd program
that comes with Apache. Storing the password file inside the web tree is a bad
idea, so place it somewhere safe outside the web tree and make sure that the
permissions on the password file only allow the owner to view and modify the
password file. Of course, the web server must be able to read the password file
too.
If you now try to access a file in the protected directory, the web
server asks the browser for a username and a password. The browser pops up a
small input box where the user can type in their username and password. If the
username/password combination matches the values in the password file, the user
is allowed to access the page. If not, they get an error page telling them the
web server could not authorize them. The realm is shown to the user so that they
know which username and password to use.
The passwords are normally
stored in an encrypted form, so even if a malicious user somehow gets their
hands on the password file, they still wouldn’t know the passwords. If you know
someone got their hands on the password file, you should probably generate new
passwords for all the users listed in that file, since it is not impossible,
though very hard, for this malicious user to come up with working passwords.
That is, if you use passwords that are sufficiently random. The function below
generates suitable passwords.
function randomPassword($length) {
$possible = '0123456789!@#$%^&*()_+' .
'abcdefghjiklmnopqrstuvwxyz' .
'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$str = "";
while (strlen($str) < $length) {
$str .= substr($possible, (rand() % strlen($possible)), 1);
}
return($str);
}
Don’t forget to initialize the random generator with srand
before calling this function or you may be generating the same set of passwords
over and over again. It might be a good idea to use mt_rand instead of rand. Not
only is mt_rand faster, it is also a better random generator and theoretically
generates better passwords. To use mt_rand you need to change one line in the
randomPassword function.
$str .= substr($possible, mt_rand(0, strlen($possible) - 1), 1);
You could initialize the random generator with something like
this (works both for mt_srand and srand):
mt_srand((double)microtime() * 1000000);
Apache also supports digest authentication. Digest
authentication works just like basic authentication, but the passwords are sent
over the Internet in an encrypted form. While this does add security, digest
authentication only works with Internet Explorer, which makes it almost
impossible to use in most cases. You use htdigest instead of htpasswd to create
the password file for digest authentication.
A small example is shown below.
<?php
if(!isset($PHP_AUTH_USER)) {
Header("WWW-Authenticate: Basic realm=\"Secret page\"");
Header("HTTP/1.0 401 Unauthorized");
echo "You did not log in correctly...\n";
exit; # exit will stop PHP from parsing the rest of the script
} else {
echo "Hello $PHP_AUTH_USER.<P>";
echo "You entered $PHP_AUTH_PW as your password.<P>";
}
?>
This can only be seen by someone who has logged in correctly.
You can put this piece of code at the top of any page that needs authentication
pages. Change the realm if you need different passwords for different sections.
You could even have it automatically included in every page with the
auto_prepend_file configuration option.
User Identification and
Authentication with PHP
Doing user authentication in PHP has a lot going for
it. It may be a bit more difficult, but the results are worth the extra effort.
Below is a list of the many advantages of doing authentication from PHP.
- It can be undone. A user can “log out”. This is not possible when you let
Apache do the authentication.
- It can expire. You can let logins expire after a certain time. For instance,
if someone logged in and did not browse your site for 30 minutes, you could
force them to authenticate themselves again.
- It can be customized. You are only limited by your imagination and your
technical skills. You have full control over the complete authentication
process. You could for instance, use a small Java applet to send encrypted
passwords over the Internet, which could be decrypted on the server with the
mcrypt library. This would work with any browser that supports Java.
- It can be database based. You can use all kinds of data from a database to
authenticate users. You could keep a complete logfile of everyone visiting your
website. (Note that Apache can get passwords from a database, for instance with
the mod_auth_mysql module.)
- It is per page. You can decide on a per-page basis if you need
authentication, which pages are authenticated and which aren't. You can do
something similar with Apache by changing the realm, but that is not as
flexible.
- It can be user authenticating and have optional registering. In registration
mode, a user without a valid login is encouraged to register and an account is
created for this user. The user is able to view the page however, with or
without an account.
- It works with CGI PHP. This is surely a big plus. While letting the
webserver do the authentication will work with the CGI version of PHP, your
script has no way of finding out who has been authenticated.
A simple
but fully functional example is shown below.
<?
if (!isset($password) || $password != "secret") {
?>
<FORM ACTION="login.php3" METHOD=POST>
<TABLE><TR><TD><CENTER>
Password: <INPUT NAME=password TYPE=password><BR>
<INPUT TYPE=SUBMIT>
</CENTER></TD></TR></TABLE>
</FORM>
<?
} else
echo "This is password protected information."
?>
You can also have the browser display a dialog where the
visitor can enter username and password information. This is done with the 401
HTTP status. This example retrieves the username and password combinations from
a MySQL database.
<?
if(!isset($PHP_AUTH_USER)) {
Header("WWW-authenticate: basic realm=\"restricted area\"");
Header( "HTTP/1.0 401 Unauthorized");
echo "You failed to provide the correct password...\n";
exit;
} else {
mysql_select_db("users");
$user_id = strtolower($PHP_AUTH_USER);
$result = mysql_query("SELECT password FROM users " .
"WHERE username = '$username'");
$row = mysql_fetch_array($result);
if ($PHP_AUTH_PW != $row["password"]) {
Header( "WWW-authenticate: basic realm=\"restricted area\"");
Header( "HTTP/1.0 401 Unauthorized");
echo "You failed to provide the correct password...\n";
exit;
}
}
?>
Only users with a working username/password combination can
see this.
You could also use a form the let the user input username
and password. That way you have complete control over the layout of the HTML
pages used.
Checking IP AddressesPeople often think an IP
address uniquely identifies a visitor. Unfortunately, this is not the case.
Proxy servers may cause the requests of several visitors to come from the same
IP address. If you were to use the IP address of those requests to identify
users, you would in fact be checking the IP of the proxy server, which is
probably not what you want. For instance, if you have an on-line poll and you
allow one vote per IP address, you may only be allowing one vote per ISP.
Another problem caused by proxy servers is the fact they may cause the requests
from a single visitor to come from several different IP addresses. Multi-user
systems and the use of IP masquerading also cause similar problems. This makes
the use of an IP address to identify a user troublesome at best.
IP
addresses do have their uses, but they are fairly limited. For instance, if you
were running a forum and you were being harassed by a user posting abusive
content, you could find out his IP address and ban anyone from that IP address.
This is a last ditch method and usually doesn’t work very well. If the he uses a
dial-up connection, he could simply reconnect to his ISP and get a new IP. If
that ISP had a proxy server, you would be banning everyone who uses that ISP to
connect to the Internet.
The following line will get the IP address that
is associated with a particular request.
$ip = $REMOTE_ADDR;
©1998 Wrox Press Limited, US and UK.