One of the nice things about the Zend Framework is being able to use it all, or only use individual parts that appeal to you. The Zend_Auth component is no exception, and makes it very easy to authenticate users. An important distinction needs to be made between authentication and authorization. Authentication is determining whether someone is who they say they are - in other words, assigning an identity to an end user. Authorization revolves around determining what actions a user can perform, and is typically handled by Zend_Acl.
Authenticate Against a Database
While using a 3rd party such as Facebook, Google, Twitter or OpenId is starting to gain a lot of traction, authenticating against a database is still one of the most common ways to authenticate a user. At it's most simplest (read the last paragraph to learn why you should never use this code), assuming we had a table called userTable with a field username for the username, and password for the password, we would have something like:
<?php
$auth = Zend_Auth::getInstance();
$db = new Zend_Db_Adapter_Pdo_Mysql(array(
'host' => 'localhost',
'username' => 'username',
'password' => 'password',
'dbname' => 'mydb'
));
$authAdapter = Zend_Auth_Adapter_DbTable($db,'userTable','username','password');
$authAdapter->setIdentity($_POST['user'])->setCredential($_POST['password']);
$result = $auth->authenticate($authAdapter);
if($result->isValid()) {
die('user authenticated');
} else {
die('user not authenticated');
}
?>
In this example, we get the Zend_Auth instance using the static function getInstance. Zend_Auth is a singleton, meaning there should only be one instance of the class during request execution. The reason for this becomes apparent when you use Zend_Auth to get a user's identity. You want to ensure you are always accessing the same instance of Zend_Auth in your code.
We then set up the database connection with the variable $db, and set up the adapter used by Zend_Auth, referenced by the variable $authAdapter. $authAdapter takes the database adapter, then the name of the table that holds end users' credentials, then the field name of said table that has the username, and finally the field name of said table that stores the password. Finally, we set the identity and credential via a $_POST variable that would be supplied on a login form, and call authenticate on Zend_Auth while passing in the adapter and check if the result is valid. All in all, pretty straightforward.
Get My Identity
Once an end user has successfully logged in, we can get the end user's identity via Zend_Auth:
<?php
$auth = Zend_Auth::getInstance();
if ($auth->hasIdentity()) {
$identity = $auth->getIdentity();
print_r($identity); // $authAdapter->setIdentity($_POST['user']);
}
?>
First, we grab the singleton Zend_Auth and then check if the user has an identity. If she does, then we get the identity. I can see the question bouncing around in your head - what is the identity. By default, when you log in with the code above, it will use the value from the setIdentity method, which is typically the user name. You can now tell whether a user is logged in, and you have their user name stored in a session.
What happens if you want to store something else besides a string as the identity? For example, you may be using Zend_Acl, and want to know the role of the user - let's create an object and store that as the identity. We have a couple of options to accomplish this, but first let's understand what Zend_Auth is doing when you call the authenticate method. Zend_Auth takes the identity returned by the Adapter (which in the case of the db adapter is a string) , and persists the identity in a session. Therefore, we could create our own adapter and return an object instead of a string, or we can interact directly with the db adapter and bypass Zend_Auth. The second option seems easiest to explain, so let's roll with that (subclassing db adapter would make a nice blog post...).
<?php
$db = new Zend_Db_Adapter_Pdo_Mysql(array(
'host' => 'localhost',
'username' => 'root',
'password' => '',
'dbname' => 'bf'
));
$authAdapter = new Zend_Auth_Adapter_DbTable($db,'userTable','username','password');
$authAdapter->setIdentity($_POST['user'])->setCredential($_POST['password']);
$result = $authAdapter->authenticate();
if($result->isValid()) {
echo 'user authenticated<br />';
} else {
echo 'user not authenticated<br />';
}
$auth = Zend_Auth::getInstance();
var_dump($auth->hasIdentity());
?>
We have similar code as before, except we are calling authenticate on the adapter. If you run this code in a browser, you would see that we authenticated the user (assuming you put in the right username and password), but we get false when calling $auth->hasIdentity(). Since we didn't use Zend_Auth to authenticate, the identity is not “saved.
To remedy this, we can update our code with the following lines:
<?php
$auth = Zend_Auth::getInstance();
$db = new Zend_Db_Adapter_Pdo_Mysql(array(
'host' => 'localhost',
'username' => 'root',
'password' => '',
'dbname' => 'bf'
));
$authAdapter = new Zend_Auth_Adapter_DbTable($db,'member','username','password');
$authAdapter->setIdentity($_POST['user'])->setCredential($_POST['password']);
$result = $authAdapter->authenticate();
if($result->isValid()) {
echo 'user authenticated<br />';
$auth->getStorage()->write($authAdapter->getResultRowObject(array(
'username', 'role'
)));
} else {
echo 'user not authenticated<br />';
}
var_dump($auth->hasIdentity()); //true
var_dump($auth->getIdentity()); // object(stdClass)#67 (2) { ["username"]=> string(3) "rob" ["role"]=> string(5) "admin" }
?>
Once the result is valid, we are getting the storage mechanism (usually a session) and telling the storage to save the result from the database as a standard object. The array of username and role are the fields we want to save. Notice we left out the password field, as that should not be persisted. For my applications, I actually have a user object that implements Zend_Acl_Role_Interface, so I instantiate my user object, and grab the values from the stdClass object that is returned from getResultRowObject.
Can You Say Insecure
What's so horrible about the above code? We are storing an end user's password in plain text within the database. If someone gains access to the database, that person now has access to all your users' password. Maybe that's not a big deal because you don't store any sensitive information like credit cards, but it's still a very bad idea. Not only will your street cred be ruined as the entire internet laughs at your folly of storing unencrypted passwords, but if you happen to store email addressees, a cracker would now have your user's email address and the password for your site. A decent percentage of your users will likely have the same password for your site and for their email account (who can remember unique passwords for every site...). You get the picture...
At a minimum, you need to encrypt the password stored in your database and add a salt to the password. You can read this article to brush up on the problems with storing passwords in a database. My next blog post will cover how to use a one way hash and a salt to make it more difficult for passwords to be cracked in the event your database is compromised.