Web Database Applications With Php And Mysql (2nd Edition) [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Web Database Applications With Php And Mysql (2nd Edition) [Electronic resources] - نسخه متنی

David Lane, Hugh E. Williams

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید








11.2 HTTP Authentication with PHP




Writing
PHP scripts to manage the authentication process allows for flexible
authorization logic. For example, an application might apply
restrictions based on group membership: a user in the finance
department gets to see the reports from the budget database, while
others can't. In another application, a user of a
subscription-based service might supply a correct username and
password, but be denied access when a fee is 14 days overdue. Or,
access might be denied on Thursday evenings during Australian Eastern
Standard Time when system maintenance is performed.

PHP scripts give you more control over the authentication process
than Apache files or configuration. In this section, we show you how
PHP scripts can use authentication credentials, and how to develop
simple, flexible authentication scripts that use HTTP.


11.2.1 Accessing User Credentials


When PHP processes a request that contains user credentials encoded
in the Authorized header field, access is provided
to those credentials through the superglobal variable
$_SERVER. The element
$_SERVER["PHP_AUTH_USER"] holds the username
that's supplied by the user, and
$_SERVER["PHP_AUTH_PW"] holds the password.

The script shown in Example 11-1 reads the
authentication superglobal variables and displays them in the body of
the response. In practice, you wouldn't display them
back to the user because it's
insecurewe've just done this to illustrate
how they can be accessed. Instead, you'd use the
credentials to authenticate the user, and allow or deny access to the
application. We explain how to do this in the next section.

For the PHP code in Example 11-1 to display the
authentication credentials, the script needs to be requested after a
user has been challenged for a username and password. For example,
the challenge can be triggered by placing the script file in a
directory configured by Apache to require authentication as discussed
in the previous section. The use of the superglobal variables
doesn't trigger authentication, it just provides
access to the values the user has provided.

Example 11-1. PHP access to authentication


<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html401/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Authentication</title>
</head>
<body>
<?php
if (isset($_SERVER["PHP_AUTH_USER"]))
print "<h2>Hi there {$_SERVER["PHP_AUTH_USER"]}</h2>";
else
print "You need to be authenticated for this to work!";
if (isset($_SERVER["PHP_AUTH_PW"]))
print "<p>Thank you for your password {$_SERVER["PHP_AUTH_PW"]}!";
?>
</body>
</html>

With access to the authentication header field information, simple
applications that rely on identifying the user can be developed. For
example, an application that charges on a per-page view basis might
use the $_SERVER["PHP_AUTH_USER"] variable when
recording an access to a particular page. In this way, Apache can
provide the authentication, and the application records the
users' behavior.

While this simple approach to developing an application removes the
need to write any PHP code to implement authentication, users and
passwords need to be maintained in an Apache password file. In the
next section, we describe how to manage HTTP authentication from
within a PHP script, thus relieving Apache of authentication
responsibilities and allowing more complex logic to be applied to
request authorization.


11.2.2 Managing HTTP Authentication with PHP


PHP scripts can manage the HTTP authentication challenges. To do
this, you check if the variables
$_SERVER["PHP_AUTH_USER"] and
$_SERVER["PHP_AUTH_PW"] are set. If
they're not, the user hasn't been
authenticated and you send a response containing the
WWW-Authenticate header to the browser. If the
variables are set, the user has answered the challenge, and you check
them against the credentials stored in the script using any logic
that's required. If the user's
credentials match those stored in the script, the user is allowed to
use the script; if not, the challenge is sent again to the browser.

In Example 11-2, the user credentials are passed to
the function authenticated(
)

. This function uses the unsophisticated
authentication scheme of checking that the password matches one
that's hard-coded into the script and, if so, it
allows the user to access the application. To test the script, you
can use any username and the password kwAlIphIdE
(the case is important). The template that's used
with the example is shown in Example 11-3.

Example 11-2. A script that generates an unauthorized response


<?php
require_once "HTML/Template/ITX.php";
require "db.inc";
function authenticated($username, $password)
{
// If either the username or the password are
// not set, the user is not authenticated
if (!isset($username) || !isset($password))
return false;
// Is the password correct?
// If so, the user is authenticated
if ($password == "kwAlIphIdE")
return true;
else
return false;
}
$template = new HTML_Template_ITX("./templates");
$template->loadTemplatefile("example.11-3.tpl", true, true);
$username = shellclean($_SERVER, "PHP_AUTH_USER", 20);
$password = shellclean($_SERVER, "PHP_AUTH_PW", 20);
if(!authenticated($username, $password))
{
// No credentials found - send an unauthorized
// challenge response
header("WWW-Authenticate: Basic realm=\"Flat Foot\");
header("HTTP/1.1 401 Unauthorized");
// Set up the body of the response that is
// displayed if the user cancels the challenge
$template->touchBlock("challenge");
$template->show( );
exit;
}
else
{
// Welcome the user now they're authenticated
$template->touchBlock("authenticated");
$template->show( );
}
?>


Example 11-3. The template that's used with Example 11-2

<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html401/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Web Database Applications</title>
</head>
<body>
<!-- BEGIN challenge -->
<h2>You need a username and password to access this service</h2>
<p>If you have lost or forgotten your password, tough!
<!-- END challenge -->
<!-- BEGIN authenticated -->
<h2>Welcome!</h2>
<!-- END authenticated -->
</body>
</html>

The authenticated(
)

function returns false
if either the $username or
$password hasn't been set, or if
the password isn't equal to the string
kwAlIphIdE. If the user credentials fail the test,
the script responds with the header field
WWW-Authenticate, and sets the encoding scheme to
Basic and the realm name to
Flat Foot. It also includes the
status code 401 Unauthorized.
The PHP manual suggests sending the
WWW-Authenticate response line before the
HTTP/1.1 401
Unauthorized response line to avoid
problems with some versions of the Internet Explorer browser.

The first time a browser requests this page, the script sends the
challenge response containing the 401 Unauthorized
header field. If the user cancels the authentication challenge,
usually by clicking the Cancel button in a dialog box that collects
the credentials, the HTML encoded in the challenge response is
displayed. When they provide the correct credentials (a username and
the password kwAlIphIdE), a welcome message is
displayed. If they don't provide the correct
credentials and don't press Cancel, the
authentication dialog is redisplayed until they do.


11.2.3 Limiting Access by IP Address


Sometimes it's useful to limit access to an
application, or part of an application, to users who are on a
particular network or using a particular machine. For example, access
to administrative functions in an application could be restricted to
a single machine, or the latest version of your application could be
limited to only those users in the testing department. In PHP,
implementing this type of restriction is straightforward: you can
check the IP address of the machine from which a request was sent by
inspecting the variable $_SERVER["REMOTE_ADDR"].
You can do the same thing in Apache, but we don't
discuss that here. (In addition, IP addresses can also be used to
help prevent session hijacking, a problem discussed later in this
chapter.)

The script shown in Example 11-4 allows access for
users who have machines on a particular network subnet. The script
limits access to the main content of the script to requests sent from
clients with a range of IP addresses that begins with
141.190.17. Because that is just the start of an
address, we test just the first 10 characters. The template used with
the example is shown in Example 11-5.

Example 11-4. PHP script that forbids access from browsers outside an IP subnet


<?php
require_once "HTML/Template/ITX.php";
$template = new HTML_Template_ITX("./templates");
$template->loadTemplatefile("example.11-5.tpl", true, true);
if(strncmp("141.190.17", $_SERVER["REMOTE_ADDR"], 10) != 0)
{
// Not allowed
header("HTTP/1.1 403 Forbidden");
$template->touchBlock("noaccess");
$template->show( );
exit;
}
else
{
// Allowed
$template->touchBlock("authenticated");
$template->show( );
}
?>


Example 11-5. The template used with Example 11-4

<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html401/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Web Database Applications</title>
</head>
<body>
<!-- BEGIN noaccess -->
<h2>403 Forbidden</h2>
<p>You cannot access this page from outside the Marketing Department.
<!-- END noaccess -->
<!-- BEGIN authenticated -->
<h2>Marketing secrets!</h2>
<p>Need new development team - the old one says <i>No</i> far too often.
<!-- END authenticated -->
</body>
</html>

There are several HTTP status codes that are appropriate to use when
denying access to a user. In the previous section, we used the
response code of 401 Unauthorized to control HTTP
authentication. However, the response status code of 403
Forbidden
is more appropriate if an explanation as to why
access has been denied is required and this is used in Example 11-4. The HTTP/1.1 standard describes 17
4xx status codes that have various meanings. The
infamous 404 Not Found is returned by Apache if
the requested resource doesn't exist, and a PHP
script can return this code if the exact reason for the refusal needs
to be hidden.


11.2.4 Authentication Using a Database




In this section, we show you how
scripts can authenticate by querying a database table that contains
usernames and passwords. Because users' credentials
are sensitive information, we show how to protect passwords with
encryption, and how the encrypted password is used in the
authentication process.

11.2.4.1 Creating a database and table


To demonstrate the principles of using a database to manage
authentication, we need a table that stores usernames and passwords,
and we need a user who can access the database and the table.
It's important to note that these are two different
issues: the database table is used to store the usernames and
passwords for the users of our application, while the MySQL database
user is just used in our PHP scripts to read and write data to the
database. We set up the database, table, and the MySQL account in
this section.

In our examples in the remainder of the chapter, we use an
authentication database that contains a
users table. To create both, you need to log in
as the MySQL root user and type the following into the MySQL command
interpreter:

mysql> create database authentication;
Query OK, 1 row affected (0.05 sec)
mysql> use authentication;
Database changed
mysql> CREATE TABLE users (
-> user_name char(50) NOT NULL,
-> password char(32) NOT NULL,
-> PRIMARY KEY (user_name)
-> ) type=MyISAM;
Query OK, 0 rows affected (0.02 sec)

The users table defines two attributes:
user_name and password. The
user_name must be unique and is defined as the
primary key.

It's also necessary to have a MySQL user that has
access to this database. You can create a user
lucy with a password secret
using the following statement, again entered into the MySQL command
interpreter:

mysql> GRANT SELECT, INSERT, UPDATE, DELETE ON authentication.users TO
-> lucy@127.0.0.1 IDENTIFIED BY 'secret';
Query OK, 0 rows affected (0.00 sec)

The syntax of this statement is discussed in Chapter 15. We use the user lucy in
our scripts in the remainder of the chapter.

11.2.4.2 Protecting passwords



Storing user
passwords as plain text represents a
security risk because insiders, external hackers, and others may gain
access to a database. Therefore, a common practice is to encrypt the
password using a non-reversible, one-way encryption algorithm and
store the encrypted version in the database. The encrypted version is
then used in the authentication process. (One-way or asymmetric
encryption is discussed later in this chapter.)

The process of protecting a password works as follows. First, a new
username and password are collected from the user. Then, the password
is encrypted and a new row is inserted into the
users table that contains the plain text
username and the encrypted password. Later, when the user returns and
wants to log in to the application, they provide their username and
password. The password provided by the user is encrypted, the row is
retrieved from the users table that matches the
provided username, and the encrypted version of the password supplied
by the user is compared to the encrypted version stored in the table.
If the username and encrypted passwords match, the credentials are
correct and the user passes the authentication.

PHP provides two functions that can be used for one-way encryption of
passwords. We define the functions next, and then show you examples
that explain their behavior in more detail.







string crypt(string
message [, string salt])


On most platforms, this function returns an encrypted string
that's calculated with a popular (if somewhat old)
encryption algorithm known as DES. The plain
text message to be encrypted is supplied
as the first argument, with an optional second argument used to
salt the DES encryption algorithm. By default,
only the first eight characters of the
message are encrypted, and the
salt
is a two-character string used by DES to make the encrypted string
harder to crack. PHP generates a random salt if one
isn't provided. The first two characters of the
returned value is the salt used in the encryption process.

As we show later, a salt is used to help prevent two passwords that
are identical being encrypted to the same string. The salt and the
password are both inputs to the encryption function and, therefore,
when two passwords are the same but have different salts, the output
is different. To encrypt another string to test if
it's the same as the encrypted string, you need to
know what salt was used so that you can re-use it. For this reason,
the salt is returned as the first two characters of the encrypted
string.

This function is one-way: the returned value can't
be decrypted back into the original string.

Several PHP constants control the encryption process, and the default
behavior is assumed in the description we've
provided. However, on some platforms, the internals of the function
actually use the MD5 approach discussed next or the salt can be
longer. You should consult the PHP manual for more details.


string md5(string
message)


Returns a 32-character message
digest


calculated from the source
message using the RSA Data Security, Inc.

MD5 Message Digest Algorithm
(http://www.faqs.org/rfcs/rfc1321l.). A
digest is a 32-character fingerprint or signature of a message, and
is not an encrypted representation of the message itself. The MD5
message digest is calculated by examining the whole message, and
messages that differ by a single character produce very different
digest results. Like the crypt( ) function,
md5( ) is one-way.

It is impossible to generate the original message from a digest. The
digest of the message is always 32 characters, and
it's not an encrypted representation of the message.
Instead, it's a string that's
calculated from the message that is almost guaranteed to be unique to
that message.

This function is widely supported on most platforms, and should be
used in preference to crypt( ) for code that
needs to be portable. Note that MD5 message digests and
Apache's Digest authentication
are unrelated concepts.



Example 11-6 shows how crypt( )
and md5( ) are used. The script generates the
following output:

md5(aardvark7) = 94198c7f71931fdeb0a7f4b75a603586
crypt(aardvark7, 'aa') = aaE/1j3.0Ky/Y
crypt(aardvark7, 'bb') = bbptug8K4z6vA
md5(aardvark8) = 4a68f92613baa5202d523134e768db13
crypt(aardvark8, 'aa') = aaE/1j3.0Ky/Y
crypt(aardvark8, 'bb') = bbptug8K4z6vA

Example 11-6. Using crypt( ) and md5( )


<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html401/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Passwords</title>
</head>
<body>
<?php
$passwords = array( );
$passwords[] = "aardvark7";
$passwords[] = "aardvark8";
foreach($passwords as $password)
{
print "\n<p> md5({$password}) = " . md5($password);
print "\n<br> crypt({$password}, 'aa') = " . crypt($password, "aa");
print "\n<br> crypt({$password}, 'bb') = " . crypt($password, "bb");
}
?>
</body>
</html>

Both functions have advantages and disadvantages:

md5( )

works with strings of any length. It
returns a fixed-length string of 32 characters
that's different if the input strings are different.
It differentiates between aardvark7 and
aardvark8 in Example 11-6 as one
would expect.

crypt( ) uses only the first eight characters of
a password and a salt to calculate the encrypted string and so, if
the first eight characters and the salt are the same, the encrypted
strings are the same. In Example 11-6, it does not
differentiate between aardvark7 and
aardvark8 when the salt is the same.

The salt in crypt( ) adds a useful extra feature
that isn't automatically supported by md5(
)
: when the string is encrypted with a different salt
string, it produces a different encrypted text even when two users
have chosen the same password. In Example 11-6, the
result of encrypting aardvark7 with the salts
aa and bb is a very different
string.


A common strategy is to use the first two characters of the username
as the salt to crypt( ). In general, this
results in different encrypted strings even if the users choose the
same password, because it's unlikely
they'd also have the same first two characters in
their username. If you want to salt the md5( )
input, you could pass both the username (or part of the username) and
the password to the md5( ) function by
concatenating the strings.

The users table has been defined to store the
32-character result of the md5( ) function. The
following fragment of code shows how the password is protected using
the md5( ) function and a new user is inserted
into the users table.

function newUser($connection, $username, $password)
{
// Create the digest of the password
$stored_password = md5(trim($password));
// Insert the user row
$query = "INSERT INTO users SET password = '$stored_password',
user_name = '$username'";
if (!$result = @ mysql_query ($query, $connection))
showerror( );
}

The function expects three parameters: a MySQL database connection
that has the authentication database as the
selected database, a plain text username, and a plain text password.
In the next section, we show you how to authenticate a user by
comparing a password that's provided by the user to
the stored password. Later in this chapter, we show you how passwords
are updated in the users table as part of a
complete authentication framework.

Because both crypt( ) and md5(
)
are one-way, after a password is stored, there is no way
to read back the original value. This prevents desirable features
such as reminding a user of his forgotten password. However,
importantly, it prevents all but the most determined attempts to get
access to the passwords.

11.2.4.3 Authenticating



When a script needs to authenticate a
username and password collected from an authentication challenge, it
needs to check the credentials against the database. To do this, the
user-supplied password is encrypted, and then a query is executed to
find a row in the users table that has a
matching username and encrypted password. If a row is found, the user
is valid.

Example 11-7 shows the authenticateUser(
)

function that validates credentials. The
function is called by passing in a handle to a connected MySQL server
that has the authentication database selected
and the username and password collected from the authentication
challenge. The script begins by testing $username
and $password, and if either variable is not set,
the function returns false. The script then
constructs a SELECT query to search the
users table using $username
and the digest of $password created using the
md5( ) function. The query is executed and if a
row is found, the $username and
$password have been authenticated, and the
function returns true.

Example 11-7. Authenticating a user against an encrypted password in the users table


<?php
function authenticateUser($connection, $username, $password)
{
// Test the username and password parameters
if (!isset($username) || !isset($password))
return false;
// Create a digest of the password collected from
// the challenge
$password_digest = md5(trim($password));
// Formulate the SQL find the user
$query = "SELECT password FROM users WHERE user_name = '{$username}'
AND password = '{$password_digest}'";
if (!$result = @ mysql_query ($query, $connection))
showerror( );
// exactly one row? then we have found the user
if (mysql_num_rows($result) != 1)
return false;
else
return true;
}
?>

The authenticateUser( ) function is likely to be
used in many scripts, so it's useful to store it in
a require file. For example, if the code is stored in the file
authentication.inc, we could rewrite Example 11-4 to use the database authentication function by
requiring the file. The rewritten version is shown in Example 11-8.


Example 11-8. A rewritten version of Example 11-4 that uses database authentication

<?php
require "authentication.inc";
require "db.inc";
require_once "HTML/Template/ITX.php";
$template = new HTML_Template_ITX("./templates");
$template->loadTemplatefile("example.11-3.tpl", true, true);
if (!($connection = mysql_connect("localhost", "lucy", "secret")))
die("Could not connect to database");
if (!mysql_selectdb("authentication", $connection))
showerror( );
$username = mysqlclean($_SERVER, "PHP_AUTH_USER", 50, $connection);
$password = mysqlclean($_SERVER, "PHP_AUTH_PW", 32, $connection);
if (!authenticateUser($connection, $username, $password))
{
// No credentials found - send an unauthorized
// challenge response
header("WWW-Authenticate: Basic realm=\"Flat Foot\");
header("HTTP/1.1 401 Unauthorized");
// Set up the body of the response that is
// displayed if the user cancels the challenge
$template->touchBlock("challenge");
$template->show( );
exit;
}
else
{
// Welcome the user now they're authenticated
$template->touchBlock("authenticated");
$template->show( );
}
?>

11.2.4.4 Encrypting other data in a database


The PHP crypt( ) and md5( )
functions can be used only to store passwords, personal
identification numbers (PINs), and so on. These functions are
one-way: after the original password is encrypted and stored, you
can't get it back (in fact, as discussed previously,
an md5( ) return value is a signature or
fingerprint and not an encrypted copy of the message). Therefore,
these functions can't be used to store sensitive
information that an application needs to retrieve. For example, you
can't use them to store and retrieve credit card
details or to encrypt a sensitive document.

To store sensitive information, you need two-way functions that use a
secret key to encrypt and decrypt the data. One significant problem
when using a key to encrypt and decrypt data is the need to securely
manage the key. The issue of key management is beyond the scope of
this book, however we discuss encryption briefly in Section 11.4.

If you need to store data using two-way encryption, a good set of
tools are in the mcrypt encryption library. PHP
provides a set of functions that access it but, to use them, you must
install the libmcrypt library and then compile PHP
with the --with-mcrypt parameter; ready-to-use
Microsoft Windows software is also available from the PHP web site.
We don't discuss the mcrypt
library in this book, but you can find more information at
http://www.php.net/manual/en/ref.mcrypt.php
and at http://mcrypt.sourceforge.net/.

MySQL also offers the reversible encode( ) and
decode( ) functions described in Chapter 15.




/ 176