Implementing swappable authentication methods

Tags:

I’ve mentioned a CMS I’m working on in a couple of occaions. Lately, I’ve been tinkering with the user authentication part of it, and stumbled upon a small obstacle: How to make it easy to change authentication methods?

Most of the time you probably would like to use a database to store your users, but at work, I had to create a way to authenticate users from Windows Active Directory using LDAP. Another case could be unit testing – you might want to test your authentication code without a database.

Idea

The idea itself is quite simple, and the most obvious way to even a beginning PHP developer would be to have a variable that is used to decide which auth method to use. Of course, this isn’t a very good implementation of the idea, but it would demonstrate it.

However, since we don’t want to use globals and all that, it would be a better idea to create a factory that creates authentication adapters. Zend Framework uses Zend_Auth_Adapter to perform authentication, but you could probably apply the method I’ll show with other frameworks too.

Scenario

So let’s say we have a class which performs authentication – we’ll call it an auth adapter. We could have an adapter for database, LDAP, testing or any authentication method you can think of. This adapter has an interface with a single method: authenticate(), which would return whether authentication was succesful or not.

But different authentication methods could require different code to initialize it. For example, a database auth method requires you to tell it which table and columns to use, LDAP could require the domain controller’s name and so on. How to allow initializing all these different methods, and even others, without creating a giant if-else or switch-case structure?

Solution

The answer lies in the factory design pattern, that I’ve mentioned in my old blog. Actually, a slightly different version of the factory pattern, called the abstract factory.

We would create factories for each different authentication type, but how would we tell the application which one we want to use? Sure, we could go into the code and change it, but it could be used in multiple places meaning we might have to edit multiple files if we wanted to change the auth method… so instead, we’ll create an abstract factory which can be used to get the factory for the chosen auth method.

abstract class AdapterFactory
{
    protected static $_factory;
 
    public static function getFactory()
    {
        return self::$_factory;
    }
 
    public static function setFactory(AdapterFactory $factory)
    {
        self::$_factory = $factory;
    }
 
    abstract public function createAdapter($params);
}

The abstract factory could look something like that. The setFactory method could be called with a concrete implementation of it in your application’s initialization, or you could even implement a configuration-file based getFactory, which would instead look at some configuration instead of returning self::$_factory.

Here’s an example of a concrete implementation:

class AdapterFactory_Db extends AdapterFactory
{
    public function createAdapter($params)
    {
        $adapter = new AuthAdapter_Db();
        $adapter->doSomething();
 
        return $adapter;
    }
}

As you can see, it’s relatively simple. Just add code for createAdapter, which returns an adapter. How about using it then?

//First, we tell which adapter we want to use
AdapterFactory::setFactory(new AdapterFactory_Db());
 
//Then this could be used to access the factory and create an adapter
$adapter = AdapterFactory::getFactory()->createAdapter(array('some' => 'parameter'));

Why all this?

Why do all these classes instead of an if-else-something?

While it may be simpler to create an if-else or a switch-case structure, this method is much more extensible and easier to manage. It also makes it much easier to create new implementations: Simply create a new factory class, which returns a different kind of an adapter – no modification needed anywhere else.

It’s essentially a choice of simplicity and maintainability/extensibility – for simple projects you probably don’t even need many ways to authenticate users, but for larger projects it’s important that they are easy to maintain and extend, as otherwise it can get very difficult to track down bugs or add new features.