Skip to end of metadata
Go to start of metadata

<h1>Introduction</h1>

<p>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.</p>

<p>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.</p>

<h1>The Pieces</h1>

<p>In order to use the HTTP Authentication adapter in an application, there are few more pieces involved beyond just the adapter class itself.</p>

<p><strong>The Pieces</strong></p>
<table><tbody>
<tr>
<td><p> index.php </p></td>
<td><p> The bootstrap file. Where things are initialized. </p></td>
</tr>
<tr>
<td><p> The HTTP Auth Adapter </p></td>
<td><p> Everything mentioned <ac:link><ri:page ri:content-title="HTTP Authentication Adapter" /><ac:link-body>previously</ac:link-body></ac:link> applies. </p></td>
</tr>
<tr>
<td><p> <code>Zend_Auth_Storage_NonPersistent</code> </p></td>
<td><p> <code>Zend_Auth</code> 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. </p></td>
</tr>
<tr>
<td><p> A Dispatcher Plugin </p></td>
<td><p> A class derived from <code>Zend_Controller_Plugin_Abstract</code>. <code>Zend_Controller_Front</code> allows plugins derived from this class to be notified on certain events in the request dispatching process. We will use this to catch the <code>dispatchLoopStartup</code> event. This will allow us to always authenticate every request, regardless of the URL, Controller, or Action. </p></td>
</tr>
<tr>
<td><p> <code>IndexController</code> </p></td>
<td><p> This example will assume the existence of a controller called <code>IndexController</code>. We will have an action method on this controller that responds to absent or invalid login credentials. </p></td>
</tr>
</tbody></table>

<h1>The Process</h1>

<h2>Overview</h2>

<p>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 <code>Zend_Auth_Storage_NonPersistent</code> class provides a way for <code>Zend_Auth</code> to avoid writing the authentication information to the session.</p>

<p>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 <code>dispatchLoopStartup</code> method, we will put the actual code that checks the user's request for authentication credentials.</p>

<p>In the example code here, we use the prefix <code>My_</code> 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.</p>

<h2>Step 1: The Bootstrap</h2>

<p>The bootstrap file, typically <code>index.php</code>, 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:</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
$config = array(
'accept_schemes' => 'digest',
'realm' => 'Example Application',
'digest_domains' => '/',
'nonce_timeout' => 3600
);
]]></ac:plain-text-body></ac:macro>

<p>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.</p>

<p>Assume that we have a password file located at <code>/usr/var/htpasswd</code>, and that this file contains a list of valid usernames and passwords, in a format readable by the provided class <code>Zend_Auth_Adapter_Http_Resolver_File</code>.</p>

<p>Now we are ready to create our objects and connect them to each other:</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
$passwd = '/usr/var/htpasswd';
$resolver = new Zend_Auth_Adapter_Http_Resolver_File($passwd);

$adapter = new Zend_Auth_Adapter_Http($config);
$adapter->setDigestResolver($resolver);
$plugin = new My_DispatcherPlugin($adapter);

$storage = new Zend_Auth_Storage_NonPersistent;
Zend_Auth::getInstance()
->setStorage($storage);

Zend_Controller_Front::getInstance()
->registerPlugin($plugin);
]]></ac:plain-text-body></ac:macro>

<p>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.</p>

<p>Here's the complete code we've added to the bootstrap:</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
$config = array(
'accept_schemes' => 'digest',
'realm' => 'Example Application',
'digest_domains' => '/',
'nonce_timeout' => 3600
);

$passwd = '/usr/var/htpasswd';
$resolver = new Zend_Auth_Adapter_Http_Resolver_File($passwd);

$adapter = new Zend_Auth_Adapter_Http($config);
$adapter->setDigestResolver($resolver);
$plugin = new My_DispatcherPlugin($adapter);

$storage = new Zend_Auth_Storage_NonPersistent;
Zend_Auth::getInstance()
->setStorage($storage);

Zend_Controller_Front::getInstance()
->registerPlugin($plugin);
]]></ac:plain-text-body></ac:macro>

<h2>Step 2: The Dispatcher Plugin</h2>

<p>The Dispatcher Plugin gets called automatically on each request by the Front Controller. We are going to implement the <code>dispatchLoopStartup</code> 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.</p>

<p>Here's the skeleton of this class:</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
require_once 'Zend/Auth.php';
require_once 'Zend/Controller/Plugin/Abstract.php';
require_once 'Zend/Auth/Adapter/Interface.php';

class My_DispatcherPlugin extends Zend_Controller_Plugin_Abstract
{
/**

  • The HTTP Auth adapter
    */
    protected $adapter;

/**

  • Constructor
    *
  • @param Zend_Auth_Adapter_Interface
    */
    public function __construct(Zend_Auth_Adapter_Interface $adapter)
    Unknown macro: { $this->adapter = $adapter; }

/**

  • Dispatch Loop Startup hook
    *
  • Called before Zend_Controller_Front enters its dispatch loop. This uses
  • the authentication adapter to check if the user submitted valid login
  • credentials. If not, the request is changed to point to the
  • authenticateAction, instead of the requested action.
    *
  • @param Zend_Controller_Request_Abstract $request
    */
    public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
    Unknown macro: { // Authentication code goes here }

    }
    ]]></ac:plain-text-body></ac:macro>

<p>Now we'll focus on <code>dispatchLoopStartup</code>:</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
{
$auth = Zend_Auth::getInstance();

$this->adapter->setRequest($this->_request);
$this->adapter->setResponse($this->_response);
$result = $auth->authenticate($this->adapter);

if (!$result->isValid())

Unknown macro: { $this->_request->setControllerName('IndexController'); $this->_request->setActionName('authenticateAction'); }

}
]]></ac:plain-text-body></ac:macro>

<p>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.</p>

<p>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 <code>authenticateAction</code> on <code>IndexController</code> 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.</p>

<h2>Step 3: The IndexController</h2>

<p>We are going to add an <code>authenticateAction</code> to our example application's <code>IndexController</code>. Since the rest of the <code>IndexController</code> isn't relevant to the discussion at hand, it's omitted. Assume we have an existing view script, called <code>401.php</code> in our view directory. This view script consists of a simple HTML page with an "Unauthorized" message, but can be whatever you want.</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
public function authenticateAction()
{
$view = $this->getInvokeArg('view');
$this->getResponse()>setBody($view>render('401.php'));
}
]]></ac:plain-text-body></ac:macro>

<p>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.</p>

<h2>User Identity</h2>

<p>Once the user has successfully logged in, you can use <code>Zend_Auth</code> anywhere to find out the username they used to log in with. For example, in a view you could display the the username:</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
$username = Zend_Auth::getInstance()->getIdentity();
echo $this->escape($username);
]]></ac:plain-text-body></ac:macro>

<h2>OK, but I need the Id...</h2>

<p>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:</p>

<ul>
<li>A factory <code>My_ModelFactory</code> that knows how to create properly DB-connected model objects</li>
<li>A <code>My_UserModel</code> class, and that <code>My_UserModel::findByUsername</code> returns an instance of <code>My_User</code></li>
<li><code>My_User</code> has several interesting properties, chiefly, one named <code>id</code></li>
</ul>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
{
$auth = Zend_Auth::getInstance();

$this->adapter->setRequest($this->_request);
$this->adapter->setResponse($this->_response);
$result = $auth->authenticate($this->adapter);

if (!$result->isValid())

else

Unknown macro: { $identity = $result->getIdentity(); $model = My_ModelFactory}

}
]]></ac:plain-text-body></ac:macro>

<p>Later, when you want to retrieve the currently logged in user's Id:</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
$user = Zend_Auth::getInstance()->getIdentity();
$userId = $user->id;
]]></ac:plain-text-body></ac:macro>

<p>It should be mentioned that the default "identity" value returned by the HTTP adapter is an array with two elements: <code>'username'</code> contains the actual login name, and <code>'realm'</code> 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.</p>

Labels:
2 2 Delete
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.