Introduction
Using the HTTP Authentication adapter alone is simple and functions. However, most real-world applications will want to use it with the Zend_Auth singleton, similarly to the other Auth Adapters. It has the added advantage of easy access the authentication results anywhere in the application.
This walks through the process of using the HTTP Authentication adapter in an application. To keep things simple, we assume the application requires a valid user account to access any part of the application; that is, there are no parts of the site with public or anonymous access.
The Pieces
In order to use the HTTP Authentication adapter in an application, there are few more pieces involved beyond just the adapter class itself.
The Pieces
| index.php | The bootstrap file. Where things are initialized. |
| The HTTP Auth Adapter | Everything mentioned previously applies. |
| Zend_Auth_Storage_NonPersistent | Zend_Auth by default stores authentication results in the session. With HTTP Authentication, this behavior is usually not desirable, so we use a different Auth Storage provider. |
| A Dispatcher Plugin | A class derived from Zend_Controller_Plugin_Abstract. Zend_Controller_Front allows plugins derived from this class to be notified on certain events in the request dispatching process. We will use this to catch the dispatchLoopStartup event. This will allow us to always authenticate every request, regardless of the URL, Controller, or Action. |
| IndexController | This example will assume the existence of a controller called IndexController. We will have an action method on this controller that responds to absent or invalid login credentials. |
The Process
Overview
Unlike forms-based authentication, HTTP Authentication is performed on each and every request made to any protected resource. Since the process is always repeated, there's no to store any authentication information in a cookie or the session. Doing so would be redundant, and probably a security a risk. The Zend_Auth_Storage_NonPersistent class provides a way for Zend_Auth to avoid writing the authentication information to the session.
Since we're assuming every page requires authentication, we need a single, central place to perform the authentication, regardless of the current request. This is what the Dispatcher Plugin is for. Inside its dispatchLoopStartup method, we will put the actual code that checks the user's request for authentication credentials.
In the example code here, we use the prefix My_ to distinguish the classes that would be provided by the user of the Zend Framework — the actual application developer who is adding HTTP authentication to their application.
Step 1: The Bootstrap
The bootstrap file, typically index.php, is where we need to set up the objects used in the authentication process. First, set up some configuration settings to define the behavior of the HTTP Authentication adapter:
This will make our HTTP adapter accept only digest authentication, and require authentication for all URLs beneath '/' (the site root). The realm string, 'Example Application', would appear in the password prompt displayed by the browser. The nonce timeout of 3600 seconds is one hour, and effectively means that the user's login is only valid for one hour. After that timeout, they will have to log in again.
Assume that we have a password file located at /usr/var/htpasswd, and that this file contains a list of valid usernames and passwords, in a format readable by the provided class Zend_Auth_Adapter_Http_Resolver_File.
Now we are ready to create our objects and connect them to each other:
Here we create a File Resolver, and pass it the path to the password file. We will use this object as the Digest Resolver for the HTTP Authentication adapter. Next, we create an instance of our Dispatcher Plugin, and pass it a reference to the HTTP adapter. It will need this reference later to perform the authentication. Then, we create the Non-Persistent storage object and tell Zend_Auth to use it as its storage medium. Lastly, we register the Dispatcher Plugin with the Front Controller so it will be called when processing a request.
Here's the complete code we've added to the bootstrap:
Step 2: The Dispatcher Plugin
The Dispatcher Plugin gets called automatically on each request by the Front Controller. We are going to implement the dispatchLoopStartup method in this class. The Front Controller calls this method right before entering its dispatch loop. This allows our plugin to potentially modify which Controller and Action the Front Controller should dispatch the request to. We are going to use Zend_Auth and the HTTP adapter to check the client request, and change the requested Controller/Action if the authentication fails.
Here's the skeleton of this class:
Now we'll focus on dispatchLoopStartup:
The HTTP Authentication adapter needs a reference to both the Request and the Response objects in order to work. Fortunately, these are both available as protected class members, inherited from the parent class. The Front Controller populates them just before calling the plugin. We use the Zend_Auth instance to do the authentication check, passing it the HTTP adapter. The HTTP adapter does all the heavy lifting of calculating the Digest authentication, and returns a Zend_Auth_Result instance with the results.
If the authentication attempt failed, we change the Controller and Action on the request to a special action dedicated just to delivering the "access denied" message. In order to do its job the HTTP Authentication adapter must manipulate the HTTP request and response headers. In the case of failed authentication, it sets the HTTP response code to 401 (Unauthorized). The only way to deliver that 401 response code to the client (and therefore deny access to the protected resource) is to have some kind of output. This is the role that our authenticateAction on IndexController fills. Note that you can set this to whatever Controller and Action you want; this is just how we've chosen to implement it in the example.
Step 3: The IndexController
We are going to add an authenticateAction to our example application's IndexController. Since the rest of the IndexController isn't relevant to the discussion at hand, it's omitted. Assume we have an existing view script, called 401.php in our view directory. This view script consists of a simple HTML page with an "Unauthorized" message, but can be whatever you want.
This is very easy. As mentioned above, it's primary purpose is to provide an outlet for the HTTP adapter's HTTP response code. And since the Front Controller and Response object automatically take care of sending the response code, we really don't have to do anything in this method. The method must exist to provide a valid action to route to in the case of authentication failure, but its mere existence is enough to get the proper status code out. On the other hand, if you have some kind of default, fall-through route (like one used for 404's) that would work to get the status code out, but without having a specific page to display an error message.
User Identity
Once the user has successfully logged in, you can use Zend_Auth anywhere to find out the username they used to log in with. For example, in a view you could display the the username:
OK, but I need the Id...
Most real-world applications have a user record of some kind in a database, and mostly need the User Id, rather than the login name. The Framework doesn't provide something to handle this right out of the box. If usernames are unique in your users table, you can modify the Dispatcher Plugin to look up the User record by username upon successful authentication. In this example, assume the following things already exist:
- A factory My_ModelFactory that knows how to create properly DB-connected model objects
- A My_UserModel class, and that My_UserModel::findByUsername returns an instance of My_User
- My_User has several interesting properties, chiefly, one named id
Later, when you want to retrieve the currently logged in user's Id:
It should be mentioned that the default "identity" value returned by the HTTP adapter is an array with two elements: 'username' contains the actual login name, and 'realm' contains the realm set in the HTTP adapter configuration. So if you happen to have a unique index on username and realm, you could use that.
ZF Home Page
Code Browser
Wiki Dashboard