More Books
PHP 5 Unleashed
PHP 5 Unleashed
Table of Contents
Copyright
Lead Author
Contributing Authors
Acknowledgments
We Want to Hear from You!
Reader Services
Introduction
Organization of the Book
Part I. Working with PHP for General Web Development
Chapter 1. Basic PHP Development
How PHP Scripts Work
Basic PHP Syntax
Basic PHP Data Types
Variable Manipulation
Control Structures
User-Defined Functions
Dynamic Variables and Functions
Multiple File PHP Scripts
References
Strings in PHP
Comparing Strings
Advanced String Comparison
Search and Replacement
Formatting Strings
Strings and Locales
Formatting Date and Time Values
Summary
Chapter 2. Arrays
Basic Arrays
Implementing Arrays
More Array Materials
Chapter 3. Regular Expressions
The Basics of Regular Expressions
Limitations of the Basic Syntax
POSIX Regular Expressions
Perl-Compatible Regular Expressions (PCRE)
PCRE Modifiers
A Few Final Words
Chapter 4. Working with Forms in PHP
HTML Forms 101
Working with Form Submissions in PHP
Summary
Chapter 5. Advanced Form Techniques
Data Manipulation and Conversion
Form Data Integrity
Form Processing
Summary
Chapter 6. Persistent Data Using Sessions and Cookies
HTTP Cookies
PHP Sessions
Advanced Sessions
Summary
Chapter 7. Using Templates
The What and Why of Templates
The Smarty Template Engine
Summary
Part II. Advanced Web Development
Chapter 8. PEAR
What Is PEAR?
Getting and Installing PEAR
Using the PEAR Package Manager
Using the PEAR Website
Using PEAR Packages in Applications
Summary
Reference
Chapter 9. XSLT and Other XML Concerns
Relating XML to HTML
Using XSLT to Describe HTML Output Using XML Input
PHP4 and XSLT Using the DOM XML Module
PHP4 and XSLT Using the XSLT Module
PHP5 and XSLT
Accessing XML Data Using SimpleXML
Generating XML Documents Using PHP
Summary
References
Chapter 10. Debugging and Optimizations
Debugging Your PHP Scripts
Optimizing Your PHP Scripts
Summary
Chapter 11. User Authentication
Authenticating Users in PHP
Securing PHP Code
Summary
Chapter 12. Data Encryption
Shared Secret Versus Public Key
Shared Secret Algorithms
Public Key Cryptography
Using Public Keys in PHP
Summary
Chapter 13. Object-Oriented Programming in PHP
Why Objects?
Creating Basic Classes
Advanced Classes
Special Methods
Class Autoloading
Object Serialization
Exceptions
Iterators
Summary
Chapter 14. Error Handling
The PHP Error-Handling Model
What to Do About Errors
The Default Error Handler
Error Suppression
Custom Error Handlers
Causing Errors
Putting It All Together
Summary
Chapter 15. Working with HTML/XHTML Using Tidy
Introduction
Basic Tidy Usage
Tidy Configuration Options
Using the Tidy Parser
Applications of Tidy
Summary
Chapter 16. Writing Email in PHP
The MIME Protocol
Implementing MIME Email in PHP
Summary
Part III. Building Applications in PHP
Chapter 17. Using PHP for Console Scripting
Core CLI Differences
Working with PHP CLI
CLI Tools and Extensions
Summary
Chapter 18. SOAP and PHP
What Are Web Services?
Installation
Creating Web Services
Consuming Web Services
Looking for Web Services
Summary
Chapter 19. Building WAP-Enabled Websites
What Is WAP?
System Requirements
Introduction to WML
Serving WAP Content
Sample Applications
Summary
Part IV. I/O, System Calls, and PHP
Chapter 20. Working with the File System
Working with Files in PHP
File Permissions
File Access Support Functions
Summary
Chapter 21. Network I/O
DNS/Reverse DNS Lookups
Socket Programming
Network Helper Functions
Summary
Chapter 22. Accessing the Underlying OS from PHP
Introduction
Unix-Specific OS Functionality
Platform-Independent System Functions
A Brief Note About Security
Summary
Part V. Working with Data in PHP
Chapter 23. Introduction to Databases
Using the MySQL Client
Basic MySQL Usage
Summary
Chapter 24. Using MySQL with PHP
Performing Queries from PHP
A MySQLi Session Handler
What Is a Custom Session Handler?
Summary
Chapter 25. Using SQLite with PHP
What Makes SQLite Unique?
Basic SQLite Functionality
Working with PHP UDFs in SQLite
Odds and Ends
Summary
Chapter 26. PHP's dba Functions
Preparations and Settings
Creating a File-Based Database
Writing Data
Reading Data
Sample Application
Conclusion
Part VI. Graphical Output with PHP
Chapter 27. Working with Images
Basic Image Creation Using GD
Using the PHP/GD Drawing Functions
Working with Colors and Brushes
Using Fonts and Printing Strings
General Image Manipulation
Other Graphics Functions
Summary
Chapter 28. Printable Document Generation
A Note Regarding the Examples in This Chapter
Generating Dynamic RTF Documents
Generating Dynamic PDF Documents
Related Resources
Part VII. Appendixes
Appendix A. Installing PHP5 and MySQL
Installing PHP5
Installing MySQL and PHP Modules
Installing PEAR
Appendix B. HTTP Reference
What Is HTTP?
PHP Programming Libraries for HTTP Work
Understanding an HTTP Transaction
HTTP Client Methods
What Comes Back: Server Response Codes
HTTP Headers
Encoding
Identifying Clients and Servers
The "Referer"
Fetching Content from an HTTP Source
Media Types
Cookies: Preserving State and a Tasty Treat
Security and Authorization
Client-Side Caching of HTTP Content
Appendix C. Migrating Applications from PHP4 to PHP5
Configuration
Object-Oriented Programming (OOP)
New Behavior of Functions
Further Reading
Appendix D. Good Programming Techniques and Performance Issues
Common Style Mistakes
Common Security Concerns
Style and SecurityLogging
Summary
Appendix E. Resources and Mailing Lists
Relevant Websites
Mailing Lists and Newsgroups
Index
SYMBOL
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
Y
Z

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.