Team LiB
Previous Section Next Section

Authenticating Users in PHP

Throughout this chapter, we will always create a sample application where a certain directory must be protected using a username and a password. There are three approaches to accomplishing this task:

  • Using HTTP authentication with Apache

  • Using HTTP authentication with PHP

  • Using PHP sessions

The required files for these secured sections of the website will be put in directories protected1, protected2, and protected3, respectively.

Why?

Creating a simple user authentication is fairly easyjust let the user provide you with a username and password. If that matches the correct values, the "secret information" is unveiled, as shown in Listing 11.1:

Listing 11.1. A Simple User Authentication Script
<html>
<head>
<title>User Authentication</title>
</head>
<body>
<?php
if (isset($_POST["user"]) && isset($_POST["pass"]) &&
  strtolower($_POST["user"]) == "shelley" && $_POST["pass"] == "deadline") {
?>
Welcome! Here is the truth about the JFK assassination ...
<?php
} else {
?>
Please log in!
<form method="post">
User name: <input type="text" name="user" /><br />
Password: <input type="password" name="pass" /><br />
<input type="submit" name="Login" />
</form>
<?php
}
?>
</body>
</html>

Most of the other authentication schemes work this way. But why is the preceding code not suitable for most websites? This way, you can protect only one page at a time, making the use for it rather limited. Most of the other protection mechanisms work for whole directories.

Using HTTP Authentication with Apache

The Apache Web server offers access control to the website using a file called .htaccess. In this file, you can provide information about who may access the website (or the current directory and its subdirectories, if you put the file in a subdirectory of the Web server), among other things.

The file .htaccess is a text file where you can provide a number of configuration options. First, you have to provide a name for the restricted area:

AuthName "PHP 5 Unleashed Protected Area"

Also, the type of authentication must be provided; in this chapter, we chose Basic:

AuthType Basic

NOTE

Other types of authentication are available, most notably digest authentication, which, however, is supported neither by old versions of Internet Explorer nor by recent versions of Netscape.


Furthermore, you have to tell Apache where the file with user credentials (name, password) is:

AuthUserFile /path/to/users

We will cover this users file in a minute.

Also, you need to tell Apache which users are allowed on your website. A good start is to allow all users that are defined in the users file.

require valid-user

This concludes the file .htaccess; Listing 11.2 is the complete code:

Listing 11.2. An .htaccess File
AuthName "PHP 5 Unleashed Protected Area"
AuthType Basic
require valid-user
AuthUserFile /path/to/users

TIP

On Windows, files that consist only of an extension (such as .htaccess) are not allowed, so you cannot use an .htaccess file there. However, if you want to develop on a Windows machine but the Web server is a Unix/Linux machine, just create a file ht.access (or any other name), copy it to your Web server, and rename it there to .htaccess.

However, if you want to use a Windows version of Apache, search for the following line in Apache's httpd.conf file:

AccessFileName .htaccess

Change this line to this command:

AccessFileName ht.access

Now, all files called ht.access will be considered as access control files. Note that you have to restart the Web server before these changes take effect.


In the next step, you have to create a users file. This contains lines that look like these:

christian:$apr1$xl......$QTjbmvK.a9Qj8kIQAu3Bf.
john:$apr1$Om......$Myf3rygKopxZfP7gVlC9o/
shelley:$apr1$fm......$WN0gyiNlFrsKgqSJrwdr4.

Each line contains a username and an associated, encrypted password, separated by a colon. But don't worry, you do not have to do this encryption by yourself. With Apache comes a useful tool called htpasswd that creates this password file (available even in Windows, in the bin subdirectory, whereas on Linux systems, it most often resides in /usr/local/apache/bin or wherever the Apache binaries are stored). The syntax for htpasswd is this:

htpasswd [options] password_file username [password]

More detailed information about this program is available when you call htpasswd without parameters. But for now, it is important to know that the switch c creates a new users file. If you omit this parameter, a new user is added to an existing file. The switch m uses the MD5 format (which is standard on the Windows platform, by the way). Here is a protocol for adding three users to a new user file:

> htpasswd m c ../users.txt christian
New password: *****
Re-type new password: *****
Adding password for user christian

> htpasswd m ../users.txt john
New password: *****
Re-type new password: *****
Adding password for user john

> htpasswd m ../users.txt shelley
New password: *****
Re-type new password: *****
Adding password for user shelley

Now place all files in the protected1 directory, the users file to the directory you provided in your .htaccess file. Try to access a document within protected1; your Web browser will ask you for a username and a password. If not, you have to tell Apache to search and parse .htaccess files. Replace

AllowOverride None

with

AllowOverride AuthConfig

in httpd.conf and restart your Web server. Figure 11.1 shows the browser's prompt for the user credentials.

Figure 11.1. The user is prompted for a name and a password.


The .htaccess way works well, but has two major flaws:

  • It is restricted to the Apache Web server.

  • Quickly adding users is a tedious taskeither htpasswd must be called using shell_exec() or system(), or passwords must be manually encrypted using PHP's crypt() function to automate the process.

NOTE

Microsoft's Web server IIS also supports basic authentication; however, there are no text files containing usernames and passwords, but existing (Windows) users on the system are used. In most cases, this is not appropriate; the next section shows you a way to use a more suitable way to authenticate users with IIS (and with Apache, as well).


Using HTTP Authentication

The title of this section is somewhat misleadingwe were using HTTP authentication in the previous section, as wellalthough with a little help from the .htaccess file. In this section, we will use a similar mechanism, but we won't rely on clumsy user files and .htaccess settings. This time, we will check the usernames and passwords within the PHP code.

To do so, you have to send some special HTTP headers to the Web browser:

header("WWW-Authenticate: Basic realm=\"PHP 5 Unleashed Protected Area\"");
header("HTTP/1.0 401 Unauthorized");

The 401 HTTP status code stands for "Not Authorized"; most Web browsers then open up a modal window where the user can enter a name and password. Depending on the browser type, this can be done an infinite number of times (Netscape browsers) or three times until an error page is displayed (Internet Explorer).

For the next examples to work, PHP must be run as a module, not in CGI mode. CGI mode will be covered later in this section.

The $_SERVER array contains the values PHP_AUTH_USER and PHP_AUTH_PW, which contain the username and the password a user entered in the modal browser window. The following code snippet checks whether $_SERVER["PHP_AUTH_USER"] is set; if so, the username and password are printed out. If not, header entries are sent so that the user is prompted to provide a username and a password, as shown in Listing 11.3 (the page after a successful login is depicted in Figure 11.2):

Listing 11.3. Username and Password Are Printed Out
<?php
if (isset($_SERVER["PHP_AUTH_USER"])) {
  echo("Username / password: ");
  echo(htmlspecialchars($_SERVER["PHP_AUTH_USER"]) .
       " / " .
       htmlspecialchars($_SERVER["PHP_AUTH_PW"]));
} else {
  header("WWW-Authenticate: Basic realm=\"PHP 5 Unleashed Protected Area\"");
  header("HTTP/1.0 401 Unauthorized");
}
?>

Figure 11.2. The user is now logged in.


However, when you try to run this script in IIS, you will get into an endless loopyou are always prompted for your password; however, it is not shown in the Web browser. This is neglected by the majority of literature on PHP.

Listing 11.4 shows a different version of the script. This time, a server variable HTTP_AUTHORIZATION is checked and printed, if available:

Listing 11.4. This Script Is Tailored for Microsoft's IIS
<?php
if (isset($_SERVER["HTTP_AUTHORIZATION"]) &&
    substr($_SERVER["HTTP_AUTHORIZATION"], 0, 6) == "Basic") {
  echo("HTTP_AUTHORIZATION: " .
       htmlspecialchars($_SERVER["HTTP_AUTHORIZATION"]));
} else {
  header("WWW-Authenticate: Basic realm=\"PHP 5 Unleashed Protected Area\"");
  header("HTTP/1.0 401 Unauthorized");
}
?>

You are again prompted for a username and password. After that, the content of the HTTP_AUTHORIZATION variable is displayed (see Figure 11.3).

Figure 11.3. The value of HTTP_AUTHORIZATION (on IIS).


NOTE

For this to work, PHP must run as ISAPI module in IIS, and you must install php5isapi.dll as ISAPI filter in the IIS admin console (see Figure 11.4). Also, you must disable Windows authentication in the IIS management console.

Figure 11.4. Install the PHP ISAPI filter.



You see that the value of HTTP_AUTHORIZATION starts with Basic, then a blank, then some garbage. However, if you look closely, you see that the characters after Basic could be Base64-encoded text. Thus, change the previous listing to the code shown in Listing 11.5 (the result can be seen in Figure 11.5):

Listing 11.5. Using base64_decode(), the User Data Is Readable
<?php
if (isset($_SERVER["HTTP_AUTHORIZATION"]) &&
    substr($_SERVER["HTTP_AUTHORIZATION"], 0, 6) == "Basic") {
  echo("HTTP_AUTHORIZATION: Basic " .
       htmlspecialchars(base64_decode(
       substr($_SERVER["HTTP_AUTHORIZATION"], 6))));
} else {
  header("WWW-Authenticate: Basic realm=\"PHP 5 Unleashed Protected Area\"");
  header("HTTP/1.0 401 Unauthorized");
}
?>

Figure 11.5. Now the username and the password are readable.


As can be seen from Figure 11.5, the content of HTTP_AUTHORIZATION (after Basic) has the following structure, if Base64-decoded:

USERNAME:PASSWORD

Thus, to retrieve the username and password for both Apache and IIS, the following code (see Listing 11.6) comes in handy:

Listing 11.6. The Username and Password Are Retrieved for Both Apache and IIS
<?php
if (isset($_SERVER["PHP_AUTH_USER"])) {
  $user = $_SERVER["PHP_AUTH_USER"];
  $pass = $_SERVER["PHP_AUTH_PW"];
} elseif (isset($_SERVER["HTTP_AUTHORIZATION"])) {
  if (substr($_SERVER["HTTP_AUTHORIZATION"], 0, 5) == "Basic") {
    $userpass = split(":",
      base64_decode(substr($_SERVER["HTTP_AUTHORIZATION"], 6)));
    $user = $userpass[0];
    $pass = $userpass[1];
  }
}

if (isset($user)) {
  echo("Username / password: ");
  echo(htmlspecialchars($user) . " / " . htmlspecialchars($pass));
} else {
  header("WWW-Authenticate: Basic realm=\"PHP 5 Unleashed Protected Area\"");
  header("HTTP/1.0 401 Unauthorized");
}
?>

NOTE

Because a Web browser stores usernames and passwords as long as the browser is not completely closed, be sure to close the browser after each example so that you start fresh without any user names or passwords being submitted. Otherwise, you might not see the pop-up windows for the user credentials, because they have already been sent automatically.


Using Static Usernames and Passwords

Using this code as a basis, HTTP authentication can be implemented rather easily. In Listing 11.7, the secret area is protected using one username/password combination: php5/iscool.

Listing 11.7. Only One Username and Password Is Valid
<?php
if (isset($_SERVER["PHP_AUTH_USER"])) {
  $user = $_SERVER["PHP_AUTH_USER"];
  $pass = $_SERVER["PHP_AUTH_PW"];
} elseif (isset($_SERVER["HTTP_AUTHORIZATION"])) {
  if (substr($_SERVER["HTTP_AUTHORIZATION"], 0, 5) == "Basic") {
    $userpass = split(":",
      base64_decode(substr($_SERVER["HTTP_AUTHORIZATION"], 6)));
    $user = $userpass[0];
    $pass = $userpass[1];  }
}

if (!isset($user) || !isset($pass) || $user!="php5" || $pass!="iscool") {
  header("WWW-Authenticate: Basic realm=\"PHP 5 Unleashed Protected Area\"");
  header("HTTP/1.0 401 Unauthorized");
} else {
  echo("Welcome, $user!");
}
?>

Only if the user enters the right credentials, the 401 Unauthorized HTTP header is not sent out to the client. To protect a site, just include the preceding file in all pages you want to secure.

TIP

Alternatively, you could use the auto_prepend_file setting in php.ini!


Of course, this code can be easily extended. For instance, you could have a whole list of valid usernames and passwords. Imagine a file where usernames and (encrypted) passwords are stored in this format:

username:encrypted_pw

The encryption is done using PHP's crypt() function. As a first parameter, the password is submitted; as a second parameter, we use the string constant "pw".

The following PHP script (see Listing 11.8 and Figure 11.6) lets the administrator enter a username and a password and writes the associated entry into a password file, using crypt():

Listing 11.8. Passwords Are Encrypted and Saved in a File
<html>
<head>
<title>User Authentication</title>
</head>
<body>
<?php
if (isset($_POST["user"]) && isset($_POST["pass"])) {
  $pwfile = fopen("users.txt", "a");
  fputs($pwfile, $_POST["user"] . ":" . crypt($_POST["pass"], "pw") . "\n");
  fclose($pwfile);
?>
user
<?php
echo htmlspecialchars($_POST["user"]) . ":" .
  crypt($_POST["pass"], "pw");
?>
 added.
<?php
}
?>
<form method="post">
User: <input type="text" name="user" /><br />
Password: <input type="password" name="pass" /><br />
<input type="submit" value="Encrypt!" />
</form>
</body>
</html>

Figure 11.6. Users can be added to the users.txt file.


CAUTION

This example is kept as easy as possible; therefore, some special security prerequisites have not been established. In a real-world application, you have to secure this script so that only you have access to it. Furthermore, the user/password file must not be readable for all users; especially, it must not be downloadable using a Web browsermove it outside the Web root.


As soon as some users are added, it is time to create a script that checks whether a given username and password exist in that filethat is, if the user is known to the system.

To do so, the username and password provided using HTTP authentication is retrieved as shown earlier. After that, the user file is parsed for this username/password combo. If successful, the user is granted access. Listing 11.9 shows the complete code, which works on both Apache and IIS.

Listing 11.9. Usernames and Passwords Are Checked Against Data in a File
<?php
if (isset($_SERVER["PHP_AUTH_USER"])) {
  $user = $_SERVER["PHP_AUTH_USER"];
  $pass = $_SERVER["PHP_AUTH_PW"];
} elseif (isset($_SERVER["HTTP_AUTHORIZATION"])) {
  if (substr($_SERVER["HTTP_AUTHORIZATION"], 0, 5) == "Basic") {
    $userpass = split(":",
      base64_decode(substr($_SERVER["HTTP_AUTHORIZATION"], 6)));
    $user = $userpass[0];
    $pass = $userpass[1];
  }
}

$auth = false;
$pwfile = fopen("users.txt", "r");
while (!feof($pwfile)) {
  $data = split(":", rtrim(fgets($pwfile, 1024)));
  if ($user == $data[0] && crypt($pass, "pw") == $data[1]) {
    $auth = true;
    break;
  }
}
fclose($pwfile);

if (!$auth) {
  header("WWW-Authenticate: Basic realm=\"PHP 5 Unleashed Protected Area\"");
  header("HTTP/1.0 401 Unauthorized");
} else {
  echo("Welcome, $user!");
}
?>

NOTE

An easy mistake: Remember that fgets() reads data until the end of the line, including the "\n" at the end. Therefore, you have to remove this character using PHP's rtrim() function.


Using Names and Passwords from a Database

The more users you get, the less performability this file-based solution will have. After some time, you will want to use a database to save user information. Again, two scripts are generated. First, the PHP page in Listing 11.10 lets you add users to the database. The database is called auth; it contains a table users with at least two fields, user and pass, both VARCHAR(255).

Listing 11.10. Passwords Are Encrypted and Saved in a Database
<html>
<head>
<title>User Authentication</title>
</head>
<body>
<?php
if (isset($_POST["user"]) && isset($_POST["pass"])) {
  $pwdb = mysql_connect("localhost", "user", "pwd");
  mysql_select_db("auth", $pwdb);
  mysql_query("INSERT INTO users (user, pass) VALUES ('" .
    $_POST["user"] . "', '" . crypt($_POST["pass"], "pw") . "')",
    $pwdb);
?>
user
<?php
echo htmlspecialchars($_POST["user"]) . ":" .
  crypt($_POST["pass"], "pw");
?>
 added.
<?php
}
?>
<form method="post">
User: <input type="text" name="user" /><br />
Password: <input type="password" name="pass" /><br />
<input type="submit" name="Encrypt!" />
</form>
</body>
</html>

The script to check submitted username/password combos is similar to the previous, file-based example; however, this time, the information is retrieved from the MySQL data source, as shown in Listing 11.11:

Listing 11.11. Usernames and Passwords Are Checked Against Data in a Database
<?php
if (isset($_SERVER["PHP_AUTH_USER"])) {
  $user = $_SERVER["PHP_AUTH_USER"];
  $pass = $_SERVER["PHP_AUTH_PW"];
} elseif (isset($_SERVER["HTTP_AUTHORIZATION"])) {
  if (substr($_SERVER["HTTP_AUTHORIZATION"], 0, 5) == "Basic") {
    $userpass = split(":",
      base64_decode(substr($_SERVER["HTTP_AUTHORIZATION"], 6)));
    $user = $userpass[0];
    $pass = $userpass[1];
  }
}

$auth = false;
$pwdb = mysql_connect("localhost", "user", "pwd");
mysql_select_db("auth", $pwdb);
$rows = mysql_query("SELECT user, pass FROM users", $pwdb);
while ($row = mysql_fetch_array($rows)) {
  if ($user == $row["user"] && crypt($pass, "pw") == $row["pass"]) {
    $auth = true;
    break;
  }
}

if (!$auth) {
  header("WWW-Authenticate: Basic realm=\"PHP 5 Unleashed Protected Area\"");
  header("HTTP/1.0 401 Unauthorized");
}
?>

The main advantage of this solution is that now you do not have to worry about things such as file locking and parallel access to the file users.txtthe database does this automatically for you. Lean back, relax, and let your users authenticate themselves.

TIP

If the Apache module mod_auth_mysql is used, the whole management and checking of usernames is even easier. The module was written by one of PHP's main developers, Zeev Suraski (the "Ze" in Zend). As of the time of writing, it is available at http://www.mysql.com/portal/software/item-241.html, but it works only on Unix/Linux. The File USAGE contains information about installation, preparing the MySQL database, and incorporating the module into your Apache Web server.


Using PHP Sessions

All previously presented methods have two major flaws:

  • They require that you have certain rights on your Web serversomething that is not true with many hosting packages.

  • They do not work in CGI mode; especially under Windows, some people still do not dare use the ISAPI module of PHP (the author of this chapter gets nervous, too, after he learned some things about the stability of this module with an older PHP version at a presentation).

One thing that always works is the use of PHP sessions. The information about whether a user is authenticated is saved in a session variable. Thanks to PHP's session management, this information is then available on all pages of the Web application.

NOTE

PHP's session-handling functions are covered in great detail in Chapter 6, "Persistent Data Using Sessions and Cookies."


Before you start, check whether all session-related information in php.ini is set:

  • The path where session data is written to must exist and be writeable for PHP (session_save_path).

  • Set session.user_cookies to 1 so that PHP always tries to set a cookie with the session ID. This makes the application more secure. Don't worry if the client does not accept cookies; the session ID is passed on using the URL.

  • If you need session-based authentication on all pages of your website, set session.auto_start to 1. If you need this authentication on only some of your pages, you should start sessions only on pages that really rely on it, using session_start().

Again, we start using a simple example where only one username/password combination is valid. The session variable username will contain the username of the currently logged-in user. If this variable does not exist, the user is not logged in. On the other hand, if the variable does exist, the user has successfully logged in.

If the session variable does not exist, a form is presented where the user can input a name and the associated password:

<form method="post">
<input type="text" name="user" /><br />
<input type="password" name="pass" /><br />
<input type="submit" name="submit" value="Login" />
</form>

After the user submits the HTML form, the name and password are checked. If the credentials are okay, the user is logged in. You must not forget that you have to set a session variable to save this "logged-in" status. Listing 11.12 contains the complete code for the login page.

Listing 11.12. A Simple Login Page
<?php
session_start();
if (isset($_POST["submit"])) {
  if ($_POST["user"] == "php5" && $_POST["pass"] == "iscool") {
    $_SESSION["username"] = $_POST["user"];
  }
}
?>
<html>
<head>
<title>User Authentication</title>
</head>
<body>
<?php
if (isset($_SESSION["username"])) {
  echo("You are logged in!");
} else {
?>
<form method="post">
<input type="text" name="user" /><br />
<input type="password" name="pass" /><br />
<input type="submit" name="submit" value="Login" />
</form>
<?php
}
?>
</body>
</html>

This works well; however, to modularize the whole login process, the user should be redirected after successfully logging inbut where?

This is where another nifty trick comes in. When linking to the login form, we submit the following as part of the URL where the user came from: http://servername/login.php?url=/path/to/origin.php. If this value is not set, however, the user is redirected to a file index.php.

Unfortunately, this simple approach creates some difficulties, depending on your PHP configuration. If you set PHP to not use cookies and/or if the user does not accept cookies for the sessions, you have to manually add the session information to the URL. You need two PHP functions for that:

  • session_name() returns the name of the PHP session (for example, "PHPSESSID").

  • session_id() returns the session ID (for example, "18143b51ee37ac73cea81cd19ba20f2c").

This leads to three cases:

  • If a session cookie exists, that is, if $_COOKIE[session_name()] is set, the user is just redirected.

  • If no session cookie exists, the session information must be appended to the URL. First, it is checked to determine whether the URL already contains a question mark. If so, "&" . session_name() . "=" . session_id() is appended (for example, "&PHPSESSID=18143b51ee37ac73cea81cd19ba20f2c").

  • If no session cookie exists, and the redirect URL also does not contain a question mark, then "?" . session_name() . "=" . session_id() is appended (for example, "?PHPSESSID=18143b51ee37ac73cea81cd19ba20f2c").

This leads to the following code:

if (!isset($_COOKIE[session_name()])) {
  if (strstr($url, "?")) {
    header("Location: " . $url .
      "&" . session_name() . "=" . session_id());
  } else {
    header("Location: " . $url .
      "?" . session_name() . "=" . session_id());
  }
} else {
  header("Location: " . $url);
}

The rest of the code is standard procedure: An HTML form accepts a username and password. Upon submitting this form, this information is checked against "php5"/"iscool". Upon success, the redirection URL is determined. Either, $_GET["src"] is set, or the standard value, "index.php", is used.

Listing 11.13 is the complete code for the login page:

Listing 11.13. A More Sophisticated Login Page
<?php
session_start();
if (isset($_POST["submit"])) {
  if ($_POST["user"] == "php5" && $_POST["pass"] == "iscool") {
    $_SESSION["username"] = $_POST["user"];
    if (isset($_GET["url"])) {
      $url = $_GET["url"];
    } else {
      $url = "index.php";
    }

    if (!isset($_COOKIE[session_name()])) {
      if (strstr($url, "?")) {
        header("Location: " . $url .
          "&" . session_name() . "=" . session_id());
      } else {
        header("Location: " . $url .
          "?" . session_name() . "=" . session_id());
      }
    } else {
      header("Location: " . $url);
    }
  }
}
?>
<html>
<head>
<title>User Authentication</title>
</head>
<body>
<form method="post">
<input type="text" name="user" /><br />
<input type="password" name="pass" /><br />
<input type="submit" name="submit" value="Login" />
</form>
</body>
</html>

Finally, you need code that checks for the session information. If $_SESSION["username"] is set, no action is required. If, however, the user is not logged in, the user must be redirected to the login page. The name of the current script ($_SERVER["SCRIPT_NAME"]) is sent to this script as a GET URL variable. Listing 11.14 is the code. Figure 11.7 shows the result in a Web browser:

Listing 11.14. If a User Is Not Logged in, the Login Form Is Loaded
<?php
session_start();
if (!isset($_SESSION["username"])) {
  header("Location: /protected3/login.php?url=" .
    urlencode($_SERVER["SCRIPT_NAME"]));
}
?>

Figure 11.7. The login pagethe referring page is seen as part of the URL.


NOTE

In this section, we will create several login pages. If you want to use one or another, you have to rename the file you want to login.php so that it is automatically called by the preceding code.


To use it, you have two possibilities:

  • Include the preceding code at the beginning of each PHP page you want to protect, before any HTTP output is sent (required for the session handling). You can use include(), require(), or require_once().

  • Include this code to all files, using auto_prepend_file in php.ini. However, before you do that, extend the code so that it checks whether it is called on a page called login.phpthe login page must be accessible without providing a password!

Using Static Usernames and Passwords

This scheme can now be applied to the other two password management approaches. First, we use the text file where all usernames and associated (crypt()-encrypted) passwords are stored. The source code for adding users to this file does not change in comparison to the code from the previous section, because the file format doesn't change. What changes, however, is the code where this information is checked. This code logic will be included in the login.php file. If an associated entry is found in the user/password file, the session variable is set accordingly. Listing 11.15 is the complete code for this page:

Listing 11.15. Login Information Is Read from a File
<?php
session_start();
if (isset($_POST["submit"])) {
  $user = $_POST["user"];
  $pass = $_POST["pass"];
  $auth = false;
  $pwfile = fopen("users.txt", "r");
  while (!feof($pwfile)) {
    $data = split(":", rtrim(fgets($pwfile, 1024)));
    if ($user == $data[0] && crypt($pass, "pw") == $data[1]) {
      $auth = true;
      break;
    }
  }
  fclose($pwfile);

  if ($auth) {
    $_SESSION["username"] = $user;
    if (isset($_GET["url"])) {
      $url = $_GET["url"];
    } else {
      $url = "index.php";
    }

    if (!isset($_COOKIE[session_name()])) {
      if (strstr($url, "?")) {
        header("Location: " . $url .
          "&" . session_name() . "=" . session_id());
      } else {
        header("Location: " . $url .
          "?" . session_name() . "=" . session_id());
      }
    } else {
      header("Location: " . $url);
    }
  }
}
?>
<html>
<head>
<title>User Authentication</title>
</head>
<body>
<form method="post">
<input type="text" name="user" /><br />
<input type="password" name="pass" /><br />
<input type="submit" name="submit" value="Login" />
</form>
</body>
</html>

Using Usernames and Passwords from a Database

If you have MySQL at hand, you can and you should store your users in the database. This makes handling users (including adding, modifying, and even deleting) much easier.

NOTE

Not sure what MySQL is or how to use it? Chapter 23, "Introduction to Databases," is a complete introduction to relational databases and Chapter 24, "Using MySQL with PHP," deals specifically with MySQL.


The database structure was explained earlierbasically, the columns user and pass contain the username and the associated password, the latter one encrypted using PHP's crypt() function. This code snippet reads out this information and compares it to the provided username and password:

$user = $_POST["user"];
$pass = $_POST["pass"];
$auth = false;
$pwdb = mysql_connect("localhost", "user", "pwd");
mysql_select_db("auth", $pwdb);
$rows = mysql_query("SELECT user, pass FROM users", $pwdb);
while ($row = mysql_fetch_array($rows)) {
  if ($user == $row["user"] && crypt($pass, "pw") == $row["pass"]) {
    $auth = true;
    break;
  }
}

The rest of the code is the same as before. The session variable is set; then the user is redirected. If necessary, the name and ID of the current PHP session are manually appended to the URL.

Listing 11.16 is the complete code for the MySQL-driven login page:

Listing 11.16. Login Information Is Loaded from a Database
<?php
session_start();
if (isset($_POST["submit"])) {
  $user = $_POST["user"];
  $pass = $_POST["pass"];
  $auth = false;
  $pwdb = mysql_connect("localhost", "user", "pwd");
  mysql_select_db("auth", $pwdb);
  $rows = mysql_query("SELECT user, pass FROM users", $pwdb);
  while ($row = mysql_fetch_array($rows)) {
    if ($user == $row["user"] && crypt($pass, "pw") == $row["pass"]) {
      $auth = true;
      break;
    }
  }

  if ($auth) {
    $_SESSION["username"] = $user;
    if (isset($_GET["url"])) {
      $url = $_GET["url"];
    } else {
      $url = "index.php";
    }

    if (!isset($_COOKIE[session_name()])) {
      if (strstr($url, "?")) {
        header("Location: " . $url .
          "&" . session_name() . "=" . session_id());
      } else {
        header("Location: " . $url .
          "?" . session_name() . "=" . session_id());
      }
    } else {
      header("Location: " . $url);
    }
  }
}
?>
<html>
<head>
<title>User Authentication</title>
</head>
<body>
<form method="post">
<input type="text" name="user" /><br />
<input type="password" name="pass" /><br />
<input type="submit" name="submit" value="Login" />
</form>
</body>
</html>

TIP

This approach is in no way restricted to the MySQL database. With very few changes to the code, this code can be adapted to PostgreSQL, MSSQL, or any other data sources. If you were using the PEAR::DB classes, this task would be limited to changing one line in the codewhere you provide the information about the connection data for the database.


    Team LiB
    Previous Section Next Section