Probably my one favorite aspects of Zend Framework is that you can pull pieces out and use them by themselves without having to build a complete MVC installation. Not that building out a complete installation is difficult but there are times when you just need a small piece, not the entire puzzle. Zend_Auth is one of those pieces that can be pulled out and used by itself. If you need authentication for an existing application Zend_Auth may just be the piece you are looking for.

Since there have been numerous tutorials written regarding Zend_Auth, I won't attempt to make a full tutorial on it. If you are curious, google around, you'll find them. Today though, I'd like to show you a cool new piece that has been added to Auth in Zend Framework 1.5, the new Zend_Auth_Adapter_OpenId.

From wikipedia:

OpenID is a decentralized single sign-on system. Using OpenID-enabled sites, web users do not need to remember traditional authentication tokens such as username and password.

Again, this isn't a tutorial on OpenID, but you will need, at the very least, an OpenID "identifier" to play along. Chances are good that you already have an OpenId even if you don't know it. AOL, Google, Microsoft and Yahoo are all OpenID providers. Good luck in finding any information on their respective implementations though. I failed to find any information on how to use Google for authentication, my Yahoo OpenID identifier failed to work with the example code (and for this I blame Yahoo) and while AOL has announced support, finding out any concrete data on how to use it. Therefore, I've fallen back to myopenid.com. It's not flashy but it does work and works well. If you have another OpenID identifier, I urge you to try it out with this code and let us know the results.

http://myopenid.com is a good, and free, provider so we'll stick with it. For the sake of this article, we will assume you either have an OpenID identifier that works or that you have gone out to http://myopenid.com and registered one.

This example does not assume you have a full Zend Framework MVC application. On the contrary, it assumes that you don't. If you were building an application based on Zend Framework's MVC, you would most likely be beaten if you tried to do authentication in this manner. The purpose of this code is two-fold. First, to show how easy it is to use OpenID to authenticate and second to show how easy it is to use pieces of Zend Framework inside your existing application.

Like the last "Lifting the Skirt" article, I'm going to give you the entire code in one fell swoop so those of you who like to cut-n-paste can just skip the rest of this. After the code, we will talk a little more about it.

<?php
/*
 * Include the needed pieces.
 */
require_once "Zend/Auth.php";
require_once "Zend/Auth/Adapter/OpenId.php";


/*
 * Initialize our variables.
 */
$status = "";
$auth = Zend_Auth::getInstance();

/*
 * There are 4 possible conditions.
 * First Condition
 * In this first test, we check to see if we already have an identity.  If we
 * do and the logout button has not been pressed, we simply display the
 * identity.  If the logout button HAS been pressed then we clear the
 * identity and notify the user.
 */
if ($auth->hasIdentity()) {
    
    if (isset($_POST['openid_action']) &&
        $_POST['openid_action'] == "Logout") {
        $auth->clearIdentity();
        $status = "You are now logged out<br>\n";
    } else {
        $status = "You are now logged in as ".$auth->getIdentity()."<br>\n";
    }
/*
 * Second Condition
 * The login button was pressed and we have an openid_identifier.  Here we
 * submit the openID identifier for authentication.  This means your page
 * will be redirected to the OpenId service provider for authentication. Any
 * code after the call to authenticate will be ignored unless something goes
 * wrong.
 *
 * If something does go horribly wrong, we notify the user and ask them to
 * look into it.
 */
} else if  (isset($_POST['openid_action']) &&
     $_POST['openid_action'] == "Login" &&
     !empty($_POST['openid_identifier'])) {

    $result = $auth->authenticate(
        new Zend_Auth_Adapter_OpenId(@$_POST['openid_identifier']));
    $status = 'Something went horribly wrong. Please consult your OpenID provider or $deity.'."<br />\n";

/*
 * Third Condition
 * This processes the callback from your OpenId service provider. This is a
 * rather simple check but it's enough to do the job. On the callback, we
 * recall authenticate() on the OpenId Adapter. Now it has all of the info
 * from your openId service provider and can make a decision as to whether or
 * not the authentication passed. If it did not pass, we do a clearIdentity()
 * just to make sure there's nothing there. In either case, we gather up any
 * messages the OpenID service provider sent and display them to the user.
 */
}else if (isset($_GET['openid_mode']) ||
          isset($_POST['openid_mode'])) {
        
    $result = $auth->authenticate(new Zend_Auth_Adapter_OpenId());

    if (!$result->isValid()) {
        $auth->clearIdentity();
    } //if (!$result->isValid())

    $status .= implode("<br />\n",$result->getMessages());
/*
 * Fourth Condition
 * The page is called and the user has not yet logged in.  This just lets
 * them know.
 */
} else {
    $status = "You are not logged in.<br />\n";
}
?>
<html><body>
<script>
<?php echo "$status";?>
<form method="post"><fieldset>
<legend>OpenID Login</legend>
<input type="text" name="openid_identifier" value="">
<input type="submit" name="openid_action" value="Login">
<input type="submit" name="openid_action" value="Logout">
<input type="submit" name="openid_action" value="Test">
</fieldset></form>

</body></html>


First, before we get into a discussion of how things work, please perform a quick check to make sure that they do. If you have your opened identifier handy, go ahead and paste it into the input box and click "Login". If you are using http://myopenid.com, when you do this, you should be redirected to their login page. (Or if you are already logged in with them then you will be presented with a page that asked you to authorize the login. Clicking on "Accept Once" should return you to your test page.

Now, you have the option of either logging out, which does not interact with your opened provider but will clear the Zend_Auth identity that was stored upon login.

The other button, "Test", will either display your OpenID identifier if you are logged in, or a message indicating that you are not logged in.

Ok, if you've tested and everything works ok then let's talk about what is actually happening.

Due to the nature of OpenID, the Zend_Auth_Adapter_OpenId is unique among the Zend_Auth_Adapters because you have to call authenticate() method twice. The first time it is called, Zend_Auth_Adapter_OpenId actually makes the call to the OpenID provider. At this point you should see your screen redirected to the provider's login or authenticate screen. Once you have told your authentication provider that you want to allow (or disallow) the login, you are redirected back to your calling page and the second call to authenticate() is made. This time, no parameters are passed because Zend_Auth_Adapter_OpenId stored off everything it needed before the first call was made. Now it can recall the necessary information, check the status returned by the provider and you can act upon it accordingly. In the case of our simple example, we notify the user of the results. You could however, redirect to a different page or branch your in-page display based on hasIdentity() which will always return true if the login was successful and false if it wasn't.

In the example above I've broken the two calls to Zend_Auth_Adapter_OpenId::authenticate() into two logical clauses for clarity. The example that this was derived from (http://framework.zend.com/manual/en/zend.auth.adapter.openid.html) shows how you could easily make it a single call.

Like everything in Zend Framework, you have many options to help you customize its operation. One of the ways you can customize the operation is by using the Zend_OpenId_Consumer_Storage_File to change where it stores its temporary files. Since we create the adapter twice (blatantly ignoring the DRY principal for the sake of clarity in the example) we have to make two changes to the code above to implement Zend_OpenId_Consumer_Storage_File. The changes are made in the Second and Third conditions.

Second Condition:

} else if  (isset($_POST['openid_action']) &&
     $_POST['openid_action'] == "Login" &&
     !empty($_POST['openid_identifier'])) {

    $authAdapter = new Zend_Auth_Adapter_OpenId($_POST['openid_identifier']);
    $authAdapterStorage = new Zend_OpenId_Consumer_Storage_File $tempDir);
    $authAdapter->setStorage($authAdapterStorage);
    $result = $auth->authenticate($authAdapter);

    $status = 'Something went horribly wrong. Please consult your OpenID provider or $deity.'."
\n";


Third Condition:

} else if (isset($_GET['openid_mode']) ||
          isset($_POST['openid_mode'])) {
        
    $authAdapter = new Zend_Auth_Adapter_OpenId();
    $authAdapterStorage = new Zend_OpenId_Consumer_Storage_File($tempDir);
    $authAdapter->setStorage($authAdapterStorage);
    $result = $auth->authenticate($authAdapter);

    if (!$result->isValid()) {
        $auth->clearIdentity();
    } //if (!$result->isValid())

    $status .= implode("
\n",$result->getMessages());


Now the only other two things we need to do is include the new class file and define $tempDir.

/*
 * Include the needed pieces.
 */
require_once "Zend/Auth.php";
require_once "Zend/Auth/Adapter/OpenId.php";
require_once "Zend/OpenId/Consumer/Storage/File.php";


/*
 * Initialize our variables.
 */
$status = "";
$auth = Zend_Auth::getInstance();
$tempDir = 'c:/web/htdocs/tmp';


Make those changes, and make sure you modify tempDir to point to an existing directory on your filesystem, and you are in business.

I hope this has given you a feel for how easy it is to implement OpenID authentication in your existing application. Working with Zend_Auth and Zend_Auth_Adapter_OpenId is really quite easy whether you are working within a Zend Framework application or not.