Handling Authentication

Exclusive offer: get 50% off this eBook here
Zend Framework 2 Cookbook

Zend Framework 2 Cookbook — Save 50%

A guide to all the ins and outs of Zend Framework 2 features with this book and ebook

$29.99    $15.00
by Josephus Callaars | December 2013 | Cookbooks Open Source Web Development

In this article by Josephus Callaars, author of Zend Framework 2 Cookbook, we will cover:

  • Understanding Authentication methods
  • Setting up a simple database Authentication

In this article we will talk about the different methods of authentication and we will show you some examples on how to authenticate and how to create your own authentication method.

(for more resources related to this topic, see here.)

Understanding Authentication methods

In a world where security on the Internet is such a big issue, the need for great authentication methods is something that cannot be missed. Therefore, Zend Framework 2 provides a range of authentication methods that suits everyone's needs.

Getting ready

To make full use of this, I recommend a working Zend Framework 2 skeleton application to be set up.

How to do it…

The following is a list of authentication methods—or as they are called adapters—that are readily available in Zend Framework 2. We will provide a small overview of the adapter, and instructions on how you can use it.

The DbTable adapter

Constructing a DbTable adapter is pretty easy, if we take a look at the following constructor:

public function __construct( // The Zend\Db\Adapter\Adapter DbAdapter $zendDb, // The table table name to query on $tableName = null, // The column that serves as 'username' $identityColumn = null, // The column that serves as 'password' $credentialColumn = null, // Any optional treatment of the password before // checking, such as MD5(?), SHA1(?), etcetera $credentialTreatment = null );

The HTTP adapter

After constructing the object we need to define the FileResolver to make sure there are actually user details parsed in.

Depending on what we configured in the accept_schemes option, the FileResolver can either be set as a BasicResolver, a DigestResolver, or both.

Let's take a quick look at how to set a FileResolver as a DigestResolver or BasicResolver (we do this in the /module/Application/src/Application/Controller/IndexController.php file):

<?php namespace Application; // Use the FileResolver, and also the Http // authentication adapter. use Zend\Authentication\Adapter\Http\FileResolver; use Zend\Authentication\Adapter\Http; use Zend\Mvc\Controller\AbstractActionController; class IndexController extends AbstractActionController { public function indexAction() { // Create a new FileResolver and read in our file to use // in the Basic authentication $basicResolver = new FileResolver(); $basicResolver->setFile( '/some/file/with/credentials.txt' ); // Now create a FileResolver to read in our Digest file $digestResolver = new FileResolver(); $digestResolver->setFile( '/some/other/file/with/credentials.txt' ); // Options doesn't really matter at this point, we can // fill them in to anything we like $adapter = new Http($options); // Now set our DigestResolver/BasicResolver, depending // on our $options set $adapter->setBasicResolver($basicResolver); $adapter->setDigestResolver($digestResolver); } }

How it works…

After two short examples, let's take a look at the other adapters available.

The DbTable adapter

Let's begin with probably the most used adapter of them all, the DbTable adapter. This adapter connects to a database and pulls the requested username/password combination from a table and, if all went well, it will return to you an identity, which is nothing more than the record that matched the username details.

To instantiate the adapter, it requires a Zend\Db\Adapter\Adapter in its constructor to connect with the database with the user details; there are also a couple of other options that can be set. Let's take a look at the definition of the constructor:

The second (tableName) option speaks for itself as it is just the table name, which we need to use to get our users, the third and the fourth (identityColumn, credentialColumn) options are logical and they represent the username and password (or what we use) columns in our table. The last option, the credentialTreatment option, however, might not make a lot of sense.

The credentialTreatment tells the adapter to treat the credentialColumn with a function before trying to query it. Examples of this could be to use the MD5 (?) function, PASSWORD (?), or SHA1 (?) function, if it was a MySQL database, but obviously this can differ per database as well. To give a small example on how the SQL can look like (the actual adapter builds this query up differently) with and without a credential treatment, take a look at the following examples:

With credential treatment:

SELECT * FROM `users` WHERE `username` = 'some_user' AND `password`
= MD5('some_password');

Without credential treatment:

SELECT * FROM `users` WHERE `username`
= 'some_user' AND `password` = 'some_password';

When defining the treatment we should always include a question mark for where the password needs to come, for example, MD5 (?) would create MD5 ('some_password'), but without the question mark it would not insert the password.

Lastly, instead of giving the options through the constructor, we can also use the setter methods for the properties: setTableName(), setIdentityColumn(), setCredentialColumn(), and setCredentialTreatment().

The HTTP adapter

The HTTP authentication adapter is an adapter that we have probably all come across at least once in our Internet lives. We can recognize the authentication when we go to a website and there is a pop up showing where we can fill in our usernames and passwords to continue.

This form of authentication is very basic, but still very effective in certain implementations, and therefore, a part of Zend Framework 2. There is only one big massive but to this authentication, and that is that it can (when using the basic authentication) send the username and password clear text through the browser (ouch!).

There is however a solution to this problem and that is to use the Digest authentication, which is also supported by this adapter.

If we take a look at the constructor of this adapter, we would see the following code line:

public function __construct(array $config);

The constructor accepts a load of keys in its config parameter, which are as follows:

  • accept_schemes: This refers to what we want to accept authentication wise; this can be basic, digest, or basic digest.
  • realm: This is a description of the realm we are in, for example Member's area. This is for the user only and is only to describe what the user is logging in for.
  • digest_domains: These are URLs for which this authentication is working for. So if a user logs in with his details on any of the URLs defined, they will work. The URLs should be defined in a space-separated (weird, right?) list, for example /members/area /members/login.
  • nonce_timeout: This will set the number of seconds the nonce (the hash users login with when we are using Digest authentication) is valid. Note, however, that nonce tracking and stale support are not implemented in Version 2.2 yet, which means it will authenticate again every time the nonce times out.
  • use_opaque: This is either true or false (by default is true) and tells our adapter to send the opaque header to the client. The opaque header is a string sent by the server, which needs to be returned back on authentication. This does not work sometimes on Microsoft Internet Explorer browsers though, as they seem to ignore that header. Ideally the opaque header is an ever-changing string, to reduce predictability, but ZF 2 doesn't randomize the string and always returns the same hash.
  • algorithm: This includes the algorithm to use for the authentication, it needs to be a supported algorithm that is defined in the supportedAlgos property. At the moment there is only MD5 though.
  • proxy_auth: This boolean (by default is false) tells us if the authentication used is a proxy Authentication or not.

It should be noted that there is a slight difference in files when using either Digest or Basic. Although both files have the same layout, they cannot be used interchangeably as the Digest requires the credentials to be MD5 hashed, while the Basic requires the credentials to be plain text. There should also always be a new line after every credential, meaning that the last line in the credential file should be empty.

The layout of a credential file is as follows:

username:realm:credentials

For example:

some_user:My Awesome Realm:clear text password

Instead of a FileResolver, one can also use the ApacheResolver which can be used to read out htpasswd generated files, which comes in handy when there is already such a file in place.

The Digest adapter

The Digest adapter is basically the Http adapter without any Basic authentication. As the idea behind it is the same as the Http adapter, we will just go on and talk about the constructor, as that is a bit different in implementation:

public function __construct($filename = null, $realm = null, $identity = null, $credential = null);

As we can see the following options can be set when constructing the object:

  • filename: This is the direct filename of the file to use with the Digest credentials, so no need to use a FileResolver with this one.
  • realm: This identifies to the user what he/she is logging on to, for example My Awesome Realm or The Dragonborn's lair. As we are immediately trying to log on when constructing this, it does need to correspond with the credential file.
  • identity: This is the username we are trying to log on with, and again it needs to resemble a user that is defined in the credential file to work.
  • credential: This is the Digest password we try to log on with, and this again needs to match the password exactly like the one in the credential file.

We can then, for example, just run $digestAdapter->getIdentity() to find out if we are successfully authenticated or not, resulting in NULL if we are not, and resulting in the identity column value if we are.

The LDAP adapter

Using the LDAP authentication is obviously a little more difficult to explain, so we will not go in to that full as that would take quite a while. What we will do is show the constructor of the LDAP adapter and explain its various options. However, if we want to know more about setting up an LDAP connection, we should take a look at the documentation of ZF2, as it is explained in there very well:

public function __construct(array $options = array(), $identity = null, $credential = null);

The options parameter in the construct refers to an array of configuration options that are compatible with the Zend\Ldap\Ldap configuration. There are literally dozens of options that can be set here so we advice to go and look at the LDAP documentation of ZF2 to know more about that. The next two parameters identity and credential are respectively the username and password again, so that explains itself really.

Once you have set up the connection with the LDAP there isn't much left to do but to get the identity and see whether we were successfully validated or not.

About Authentication

Authentication in Zend Framework 2 works through specific adapters, which are always an implementation of the Zend\Authentication\Adapter\AdapterInterface and thus, always provides the methods defined in there. However, the methods of Authentication are all different, and strong knowledge of the methods displayed previously is always a requirement. Some work through the browser, like the Http and Digest adapter, and others just require us to create a whole implementation like the LDAP and the DbTable adapter.

Zend Framework 2 Cookbook A guide to all the ins and outs of Zend Framework 2 features with this book and ebook
Published: December 2013
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

Setting up a simple database Authentication

After seeing all the authentication methods available, it is time to see how it will actually work when we have a database authentication in place. Here we will look at all the ins and outs of this specific method.

Getting ready

A working Zend Framework 2 skeleton application with the PHP sqlite extension loaded and enabled.

How to do it…

Database authentication can very well be the most widely used authentication method there is. Here we will set up our own database authentication.

Setting up the module initialization

We will create our database as soon as possible after initialization of the modules, so we will attach it to an event called route or MvcEvent::EVENT_ROUTE. As a template for the Module.php we can just copy over the Application/Module.php file and change the namespace; we will be working in the onBootstrap method anyway, and the rest of the Module class can stay the same (but don't forget to change the namespace!).

Let's take a look at the code of our /module/Authentication/Module.php file:

// We can assume the rest of the Module class file is // exactly the same as the default // Application/Module.php file, except of course the // namespace. public function onBootstrap(MvcEvent $e) { // This is also default $eventManager = $e->getApplication()->getEventManager(); $moduleRouteListener = new ModuleRouteListener(); $moduleRouteListener->attach($eventManager); // And now we let the magic happen (this is the bit we // will insert) $eventManager->attach( // We want to attach to the route event, which means // it happens before our controllers are initialized // (because that would mean we already found the // route) MvcEvent::EVENT_ROUTE, // We are using this function as our callback function (MvcEvent $event) { // Get the database adapter from the configuration $dbAdapter = $event->getApplication() ->getServiceManager() ->get('db'); // Our example is an in memory database, so the // table never exists, but better sure than sorry $result = $dbAdapter->query(" SELECT name FROM sqlite_master WHERE type='table' AND name='users' ")->execute(); // If we couldn't find a users table, we will // create one now (with an in memory db this is // always the case) if ($result->current() === false) { try { // The user table doesn't exist yet, so let's // just create some sample data $result = $dbAdapter->query(" CREATE TABLE `users` ( `id` INT(10) NOT NULL, `username` VARCHAR(20) NOT NULL, `password` CHAR(32) NOT NULL, PRIMARY KEY (`id`) ) ")->execute(); // Now insert some users $dbAdapter->query(" INSERT INTO `users` VALUES (1, 'admin', '". md5("adminpassword"). "') ")->execute(); $dbAdapter->query(" INSERT INTO `users` VALUES (2, 'test', '". md5("testpassword"). "') ")->execute(); } catch (\Exception $e) { \Zend\Debug\Debug::dump($e->getMessage()); } } }); }

We have now created an event that will be triggered when we start routing. If we look carefully enough we can find one big mistake that will crash this code for sure. The problem of course being the db key in the ServiceManager, as we refer to a service we have yet to create. So let's get cracking and create that /module/Authentication/config/module.config.php file:

<?php return array( // Let's initialize the ServiceManager 'service_manager' => array( 'factories' => array( // Create a Db Adapter on initialization of the // ServiceManager 'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory', ), // Let's give this Db Adapter the alias db 'aliases' => array( 'db' => 'Zend\Db\Adapter\Adapter', ), ), // We will now configure our Sqlite database, for // which we only need these two lines 'db' => array( 'driver' => 'Pdo_Sqlite', 'database' => ':memory:', ), );

That's it; our basic configuration to get the database going is done, and if we run the code now we can be certain our database is created.

Creating the authentication service

The next thing we want to do is to create our Authentication service, the service that will help our application do all the authentication functionality. Let's create this service in the Authentication\Service namespace, and let's call the class Authentication (the file is /module/Authentication/src/Authentication/Service/Authentication.php).

<?php // Set the namespace namespace Authentication\Service; use Zend\ServiceManager\ServiceLocatorAwareInterface; // We give this one an alias, because otherwise // DbTable might confuse us in thinking that it is // an actual db table use Zend\Authentication\Adapter\DbTable as AuthDbTable; use Zend\Authentication\Storage\Session; // We want to make a service, so we implement the // ServiceLocatorAwareInterface for that as well class Authentication implements ServiceLocatorAwareInterface { // Storage for our service locator private $servicelocator; // Get the ServiceManager public function getServiceLocator() { return $this->servicelocator; } // Set the ServiceManager public function setServiceLocator( \Zend\ServiceManager\ServiceLocatorInterface $serviceLocator) { $this->servicelocator = $serviceLocator; }

Well that was easy; we just created our service… which does absolutely nothing at the moment. Let's first create a method that checks if we are authenticated or not. We do this by checking the authentication session, and see if it is empty or not. Assuming that in this case we only have a (authentication!) session when are actually authenticated, we can safely agree that we will be logged in;

/** * Lets us know if we are authenticated or not. * * @return boolean */ public function isAuthenticated() { // Check if the authentication session is empty, if // not we assume we are authenticated $session = new Session(); // Return false if the session IS empty, and true if // the session ISN'T empty return !$session->isEmpty(); }

We can easily just open a session as the namespace of the session will only be used for authentication purposes.

Let's now create our authentication, which will authenticate a username and password, and return a boolean stating that we are or aren't successful in authenticating:

/** * Authenticates the user against the Authentication * adapter. * * @param string $username * @param string $password * @return boolean */ public function authenticate($username, $password) { // Create our authentication adapter, and set our // DbAdapter (the one we created before) by getting // it from the ServiceManager. Also tell the adapter // to use table 'users', where 'username' is the // identity and 'password' is the credential column $authentication = new AuthDbTable( $this->getServiceLocator()->get('db'), 'users', 'username', 'password' ); // We use md5 in here because SQLite doesn't have // any functionality to encrypt strings $result = $authentication->setIdentity($username) ->setCredential(md5($password)) ->authenticate(); // Check if we are successfully authenticated or not if ($result->isValid() === true) { // Now save the identity to the session $session = new Session(); $session->write($result->getIdentity()); } return $result->isValid(); }

As we saw in the previous code snippet, we created a simple authentication method that returns either true or false, depending on if we are authenticated or not. What it also does is save the identity to the authentication session, so we can see in our previous method if we were authenticated or not. We also need the identity in the session for when we want to get the username from our logged in user, which will retrieve with the following method:

/** * Gets the identity of the user, if available, * otherwise returns false. * @return array */ public function getIdentity() { // Clear out the session, we are done here $session = new Session(); // Check if the session is empty, if not return the // identity of the logged in user if ($session->isEmpty() === false) { return $session->read(); } else { return false; } }

Now that we got our identity, it is also important that we are able to logout. In our case it is as simple as just clearing the session, because why would we make it more difficult than just that?

/** * Logs the user out by clearing the session. */ public function logout() { // Clear out the session, we are done here $session = new Session(); $session->clear(); } // This is our last method, close the bracket for the // class as well! }

We have now created a simple authentication service, and the only part left now is to register it in the service manager so that it will be instantiated when we boot up. We can do this in the /module/Authentication/config/module.config.php file as usual, and because we already have a service_manager configuration there, we can just plant the invokable in there:

<?php return array( 'service_manager' => array( // [The rest of the service manager configuration // comes here] // And our new invokable can be put here 'invokables' => array( 'AuthService' => 'Authentication\Service\Authentication', ), ), );

And that's it for the service! All that is left now to do is create the login/logout action and then check if we are logged in or not. Let's begin with the login/logout action so that we are actually able to login!

Setting up the controller and action

Let's first change the /module/Authentication/config/module.config.php file while we are still in there so we can access our login/logout action, which is kind of crucial to us:

<?php return array( // [The configuration that we have now resides here..] // And our route configuration comes here.. 'router' => array( 'routes' => array( 'authentication' => array( 'type' => 'Literal', 'options' => array( 'route' => '/authentication', 'defaults' => array( '__NAMESPACE__' => 'Authentication\Controller', 'controller' => 'Index', 'action' => 'login', ), ), 'may_terminate' => true, 'child_routes' => array( 'default' => array( 'type' => 'Segment', 'options' => array( 'route' => '[/:action]', 'constraints' => array( 'action' => '[a-zA-Z][a-zA-Z0-9_-]*', ), 'defaults' => array(), ), ), ), ), ), ), // Make our controller invokable 'controllers' => array( 'invokables' => array( 'Authentication\Controller\Index' => 'Authentication\Controller\IndexController' ), ), // Make sure our template path is set correctly 'view_manager' => array( 'template_path_stack' => array( __DIR__ . '/../view', ), ), );

This basic route just makes /authentication redirect to our loginAction and because of the segment route we can simply do /authentication/logout to redirect to our logoutAction.

Let's continue creating our /module/Authentication/src/Authentication/Controller/IndexController in the Authentication\Controller namespace:

<?php namespace Authentication\Controller; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\ViewModel; class IndexController extends AbstractActionController { }

We have simply declared our controller; now let's add the logoutAction (we will begin with that, as it is incredibly simple) and the loginAction:

public function logoutAction() { // Log out the user $this->getServiceLocator() ->get('AuthService') ->logout(); // Redirect the user back to the login screen $this->redirect() ->toRoute('authentication'); }

As we can see this is almost too simple, but we won't complain if it works. Now let us create our loginAction, which basically looks if there is a post and if there is tries to login, otherwise shows a login form. Upon successful login we will be redirected to the /application route, and if not successful we will just display an error message:

public function loginAction() { // See if we are trying to authenticate if ($this->params()->fromPost('username') !== null) { // Try to authenticate with our post variables from // the form we just send $done = $this->getServiceLocator() ->get('AuthService') ->authenticate( $this->params()->fromPost('username'), $this->params()->fromPost('password') ); if ($done === true) { $this->redirect() ->toRoute('application'); } else { \Zend\Debug\Debug::dump( "Username/password unknown!" ); } } // On an unsuccessful attempt or just a get request // show the form. return new ViewModel(); }

As we can see the loginAction is merely checking if we have anything posted, and if we do, it lets the AuthService handle it. This way is not perfect as it doesn't check for malicious parameters or anything, but it does show how clean a controller is supposed to be with no login in there except the bare minimum parsing of variables.

The logoutAction doesn't contain a view script, as that action only redirects the user and never has a response of its own. The loginAction, however, does have view script, as it needs to show a form. Let's quickly build a view script for the loginAction now (the file is /module/Authentication/view/authentication/index/login.phtml):

<form action="/authentication" method="post"> <label for="username">Username:</label> <input type="text" name="username" /> <label for="password">Password:</label> <input type="password" name="password" /> <button type="submit">Login</button> </form>

A simple form to login and in my opinion doesn't require any explanation.

The last thing that we want to do know is to make sure nobody can access anything in the application other than the authentication if he/she is not logged in. We can do that by a new event in the Module (the file is /module/Authentication/Module.php) class of the Authentication module, which will check if we are logged in, and if not redirects us before any output is done to the screen:

public function onBootstrap(MvcEvent $e) { // Get the event manager from the event $eventManager = $e->getApplication()->getEventManager(); // Attach the module route listeners $moduleRouteListener = new ModuleRouteListener(); $moduleRouteListener->attach($eventManager); // Do this event when dispatch is triggered, on the // highest priority (1) $eventManager->attach( MvcEvent::EVENT_DISPATCH, function (MvcEvent $event) { // We don't have to redirect if we are in a // 'public' area, so don't even try if ($event->getRouteMatch()->getMatchedRouteName() === 'authentication') return; // See if we are authenticated, if not lets // redirect to our login page if ($event->getApplication()->getServiceManager() ->get('AuthService')->isAuthenticated() === false) { // Get the response from the event $response = $event->getResponse(); // Clear current headers and add our Location // redirection $response->getHeaders() ->clearHeaders() ->addHeaderLine( 'Location', '/authentication' ); // Set the status code to redirect $response->setStatusCode(302) ->sendHeaders(); // Don't forget to exit the application, as we // don't want anything to overrule at this point exit; } }, // Give this event priority 1 1); }

That's the event that we need, what happens is that we will simply be redirected to the login page whenever we try to reach a route which is not our authentication route.

How it works…

What we are going to do is create a simple database authentication that works through an in-memory SQLite database. This means that the database isn't stored and that all the tables and records need building up every time we request the page. Obviously this is highly inconvenient to use in a production environment, it is, however, excellent to show off how it works and is really handy to get something going quickly.

Assuming we are working on a default Zend Skeleton application, let's create a new module Authentication. This new module will contain the database connection, the authentication itself and the login and logout actions. When we created the directory for the new module, we should also be wary to add the new module in the application.config.php file, otherwise we might end up having trouble finding out why it doesn't do anything (oh yes, I am talking from experience).

First of all we built our in-memory database in the Module.php for the authentication. We then created a table called users, with a unique ID, username, and password. The ID consist of an integer, the username a variable character of 20 positions, and the password will be a character of 32, as that is the size of an MD5 encrypted string.

Because we set up a user table, and connected that table to the authentication adapter, we were able to authenticate the username and password simply. As an extra measure we made sure the user can't go to any other page than the login page when he isn't logged in, which we did by using an event that happens before the output was send to the user.

Summary

In this article, we learned how we can create our own authentication method. We went through Authentication methods, and learned how to set up a simple database Authentication.

Resources for article:


Further resources on this subject:


Zend Framework 2 Cookbook A guide to all the ins and outs of Zend Framework 2 features with this book and ebook
Published: December 2013
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

About the Author :


Josephus Callaars

Josephus Callaars is a software developer whose passion began like so many other developers at the appropriate age of twelve. Being intrigued by mathematics and software languages such as Assembler and Java, he quickly found out that his career was to be found in the abstract side. Since 2003 he has been developing software applications commercially, and always tried to stay up-to-date with the latest technologies.

Josephus has been passionate about developing ever since, and always thought it could be done better every time. He is a Zend Certified Engineer and is regularly to be found in the open source community, where he is always on the lookout for new things to learn.

Books From Packt


 Zend Framework 2.0 by Example: Beginner’s Guide
Zend Framework 2.0 by Example: Beginner’s Guide

 Instant Zend Framework 2.0
Instant Zend Framework 2.0

Zend Framework 2 Application Development
Zend Framework 2 Application Development

Zend Framework 1.8 Web Application Development
Zend Framework 1.8 Web Application Development

 RESTful PHP Web Services
RESTful PHP Web Services

 Expert PHP 5 Tools
Expert PHP 5 Tools

Magento 1.3: PHP Developer's Guide
Magento 1.3: PHP Developer's Guide

 PHP Application Development with NetBeans: Beginner's Guide
PHP Application Development with NetBeans: Beginner's Guide


No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
4
E
S
s
7
S
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software