"The human factor is truly security's weakest link" Kevin D. Mitnick

Build a secure login with Zend Framework

Posted: July 3rd, 2009 | Author: | Filed under: Zend Framework | Tags: , , , , , , , , , , , | 61 Comments »

After a long pause i’m come back on my blog with a post about the development of a secure web login system in PHP with the use of Zend Framework. The issue to build a login system in a web application is quite common. The security aspect of a login system is absolutely important and in this post i have summarized some of the main security points. Of course this is not the final solution but, in my opinion, is a good point of start for PHP developers.

I have built a simple Zend Framework application with a login and home page. The login is a form with a username and a password text box. This fields are mapped into a MySQL database, with a simple table with the following structure:

1
2
3
4
5
6
7
8
9
CREATE TABLE users (
       username VARCHAR(32) NOT NULL
     , password CHAR(32) NOT NULL
     , salt CHAR(20) NOT NULL
     , email VARCHAR(80)
     , active BOOLEAN NOT NULL DEFAULT 1
     , last_access DATETIME NOT NULL
     , PRIMARY KEY (username)
);

As you can see we store the password with a CHAR(32) field, this is because we use the MD5() hash function to secure the storing of the data. As you know if you store a password with an hash function is, in theory, impossible to invert the hash value and find the password in plain. I wrote in theory because, as Joe Devon writes in his comment, you can use Google to retrieves the most common hash values (for instance try this web site http://md5.rednoize.com/).
In order to improve the security of the MD5 i used a salt value. I added a salt field of CHAR(20) into the users table with a random string value. The MD5 of the password is generated with a MD5(CONCAT(salt,’password’)) where ‘password’ is the password string value. With this salt method is more difficult to use a dictionary attack of the MD5 values.
With this system even the administrator of the site can’t know the password of their users. This is very important for privacy reason (do you trust websites that are able to recover your password?).

This application uses the following Zend Framework classes to build the secure login:

  • Zend_Form: to build the login page;
  • Zend_Auth_Adapter_DbTable: to authenticate the users stored into the database;
  • Zend_Session: to store the data into the Session;
  • Zend_Config: to store the configuration file of the application;
  • Zend_Db_Table: to map the table of the database into a PHP class;

The structure of the application

I have structured the application into the following directories:

Directories
The main directoris are: application, etc, library, public.
In application i put the usual MVC structure of a Zend Framework, with the files bootstrap.php and Initializer.php. The Initializer class is a plug-in of the Front Controller where i wrote the authentication check of the login. I think it’s a good solution for a login system because in this way we can manage the authentication system without any modification of the Controllers. This means that if a user is able to see a Controllers he is just authenticated.
In the etc directory i put the configuration file config.ini and the database structure into the sub-dir db.
In the library directory i put the common php classes that i reuse in my Zend Framework applications and a sub-directory name Forms with all the forms of the application.
In the public directory i put the document root of the application with .css, .js, images and the index.php that is the only php scripts published into the document root.

We focus now on the Initializer plugin, that is the core of the login system.
This Initializer plugin comes from the structure proposed by the Zend Studio for Eclipse where i have inserted some more methods.
In particular i have added the preDispatch and checkSession methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * preDispatch
 */
public function preDispatch () {   
  $this->_controller= $this->_front->getRequest()->getControllerName();
  if (($this->_controller!='index') && ($this->_controller!='error')) {
    if (Globals::getConfig()->authentication->active) {           	
      $this->checkSession();
    } 
  }
}
/**
 * checkSession
 */
private function checkSession() {
  if (empty(Globals::getSession()->username)) {
    $this->_response->setRedirect('/index/login')->sendResponse();
    exit;
  }
}

The preDispatch method is called from the Zend Framework flow first to dispatch every actions and for this reason it’s a good point where insert an authentication system. In the preDispatch method we get the name of the Controller and we check if the actual controller is different from index and error, if it’s true i call the checkSession method.
Notice that i used another if to check if the authentication system is on, using the attribute active of the config.ini file. This system can be useful to switch on and off the authentication, changing a single value into a configuration file.
The index and error are the controllers out of the authentication system, this because they contain public pages that must be visible without autentication.
The checkSession method is very simple, it checks if the session variable username is empty, if it is empty means that the user is not authenticated and the execution flow must be redirect to the login page. Note the presence of the exit() function, it’s very important because without it an authorized user can execute actions because the flow of Zend Framework continues after the sendResponse (thanks to Ankit for his comment).
As you have noticed i used the Globals class to take some singleton objects related to the management of the database conncetion, the session and the configuration file. I usually built these singletons because i have only one instance of database, session and configuration file during the execution of my PHP applications. Moreover with a singleton i can speed up the reading of these values during the execution of the same instance of an application.

The login form

The login form of the application is built into the library/Forms/Login.php script. I used the Zend_Form_Element_Hash class of the Zend Framework to secure against CSRF attacks (click here for info about CSRF attacks). I used the setSalt method to improve the security with a pseudo-random value obtained with the function md5(uniqid(rand(), TRUE)) and the setTimeout to set a Time To Live (TTL) for the token (the value of timeout is specified into the config.ini).

...
$token = new Zend_Form_Element_Hash('token');
$token->setSalt(md5(uniqid(rand(), TRUE)));
$token->setTimeout(Globals::getConfig()->authentication->timeout);
$this->addElement($token);
...

With this hidden value, the token, inserted into a form we are sure that the POST data comes from our page and not from others.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
...
public function loginAction() {
  $flash = $this->_helper->getHelper('flashMessenger');
  if ($flash->hasMessages()) {
    $this->view->message = $flash->getMessages();
  }         
  $this->view->form= new Forms_Login();
  $this->render('login'); 
}
...
public function submitAction() {
  $form= new Forms_Login();      
  if (!$form->isValid($_POST)) {
    if (count($form->getErrors('token')) > 0) {
      return $this->_forward('csrf-forbidden', 'error');
    } else {
      $this->view->form = $form;
      return $this->render('login');
    }	
  }   
  $username= $this->getRequest()->getPost('username');
  $password= $this->getRequest()->getPost('password');        
  $authAdapter = new Zend_Auth_Adapter_DbTable(
                               Globals::getDbConnection(),
    				'users',
    				'username',
    				'password',
                        	'MD5(CONCAT(salt,?)) AND active=1'
                             );
  $authAdapter->setIdentity($username)
                        ->setCredential($password);
  $result= $authAdapter->authenticate();
  Zend_Session::regenerateId();      
  if (!$result->isValid()) {        
    $this->_helper->flashMessenger->addMessage("Authentication error.");
    $this->_redirect('/index/login');   
  } else {
    Globals::getSession()->username= $result->getIdentity();
    Zend_Loader::loadClass('Users');
    $users= new Users();
    $data= array ('last_access' => date('Y-m-d H:i:s'));
    $where= $users->getAdapter()->quoteInto('username = ?', Globals::getSession()->username);
    if (!$users->update($data,$where)) {
      throw new Zend_Exception('Error on update last_access');
    }
    $this->_redirect('/home');
  }
}
...

In the loginAction i built the login form (line 7).
The data of the login page are submitted to the submitAction method. In this action we check the validation of the form (line 13). If there’s a problem on the token data this means that we are under a CSRF attack so we can redirect to a specific error page named csrf-forbidden. The csrf-forbidden page gives a 403 Forbidden value, this to prevent that we send the form data to an unauthorized user (for more info about this subject read the post “Preventing CSRF properly” of Tom Graham).

For the authentication of the user we used the Zend_Auth_Adapter_DbTable adapter (line 23). We check the authorization using the MD5 of the password (MD5(?), where ? is $password) only for the active users (active=1).
If the user is authenticated we update the last_access field of the user table and we redirect to the home page (lines 38-46).
If the user is not authenticated we redirect to the login page with the error message ‘Authentication error.’.
Notice that we regenerated the session id with the Zend_Session::regenerateId() during the login (line 33) and we clean the session during the logout (line 25 of the logoutAction into the HomeController). This for security reason in order to mitigate the possibility of a session fixation attack (click here for info about session fixation).

In this post i didn’t talk about SQL Injection, why? Because the Zend_Db class that we used in this application uses placeholder quoting to prevent SQL Injection. Moreover we used the validator of the Zend_Form in order to filter the input values of username and password (see the addValidators method in the Forms_Login class). So the mantra “Filter Input, Escape Output” is respected.

Conclusion

In this post i provided a secure login application with the use of Zend Framework. The securiy aspect of this application are:

  • MD5 + salt of the passwords stored into the database;
  • pseudo-random token generated into the form to prevent CSRF attacks;
  • timeout of the token validity to improve the security of the login system;
  • regeneration of the session ID to mitigate the possibility of session fixation attacks;
  • redirect to a 403 Forbidden page in presence of a CSRF attack;
  • Zend_DB uses placeholder quoting to prevent SQL Injection attacks;

One of the unsecure point of this application is that the username and the password are submitted in plain text. Any attacker that is able to sniff the HTTP communication between client and server can capture these information.
In order to build a real secure system i suggest to use the Secure Sockets Layer protocol (SSL), this is the only way to encrypt the communication between client and server.

Source code

Here you can find the source code of the Secure Login Application. In order to execute the code you have to create a MySQL database using the SQL structure reported in /etc/db/dbUsers.sql and modify the etc/config.ini with your db permission.
I tested the application with the Zend Framework v. 1.8.4.

Click here to download the source code (ZFSecureLogin.zip, 13Kb)