Middleware authentication

Many web applications require restricting specific areas to authenticated
users, and may further restrict specific actions to authorized user roles.
Implementing authentication and authorization in a PHP application is often
non-trivial as doing so requires altering the application workflow. For
instance, if you have an MVC design, you may need to change the dispatch logic
to add an authentication layer as an initial event in the execution flow, and
perhaps apply restrictions within your controllers.

Using a middleware approach is simpler and more natural, as middleware easily
accommodates workflow changes. In this article, we will demonstrate how to
provide authentication in a PSR-7 middleware application usingExpressive andzend-authentication. We
will build a simple authentication system using a login page with username and
password credentials.

Since the content of this post is quite long, we’ll detail authorization
in a separate blog post.

Getting started

This article assumes you have already created an Expressive application. For the
purposes of our application, we’ll create a new module, Auth, in which we’ll
put our classes, middleware, and general configuration.

First, if you have not already, install the tooling support:

$ composer require --dev zendframework/zend-expressive-tooling

Next, we’ll create the Auth module:

$ ./vendor/bin/expressive module:create Auth

With that out of the way, we can get started.

Authentication

The zend-authentication component offers an adapter-based authentication
solution, with both a number of concrete adapters as well as mechanisms for
creating and consuming custom adapters.

The component exposes ZendAuthenticationAdapterAdapterInterface, which
defines a single authenticate() method:

Adapters implementing the authenticate() method perform the logic necessary to
authenticate a request, and return the results via aZendAuthenticationResult object. This Result object contains the
authentication result code and, in the case of success, the user’s identity.
The authentication result codes are defined using the following constants:

We will want a factory for this service as well, so that we can seed the
username and password to it later:

// In src/Auth/src/MyAuthAdapterFactory.php:
namespace Auth;
use InteropContainerContainerInterface;
use ZendAuthenticationAuthenticationService;
class MyAuthAdapterFactory
{
public function __invoke(ContainerInterface $container)
{
// Retrieve any dependencies from the container when creating the instance
return new MyAuthAdapter(/* any dependencies */);
}
}

This factory class creates and returns an instance of MyAuthAdapter.
We may need to pass some dependencies to its constructor, such as a database
connection; these would be fetched from the container.

Authentication Service

We can now create a ZendAuthenticationAuthenticationService
that composes our adapter, and then consume the AuthenticationService in
middleware to check for a valid user. Let’s now create a factory for theAuthenticationService:

Use zend-servicemanager’s ReflectionBasedAbstractFactory

If you are using zend-servicemanager in your application, you could skip the
step of creating the factory, and instead map the middleware toZendServiceManagerAbstractFactoryReflectionBasedAbstractFactory.

Finally, we can create appropriate routes. We’ll map /login to theLoginAction now, and allow it to react to either the GET or POST methods:

Authentication middleware

Now that we have the authentication service and its adapter and the login
middleware in place, we can create middleware that checks for authenticated
users, having it redirect to the /login page if the user is not authenticated.

This middleware checks for a valid identity using the hasIdentity() method ofAuthenticationService. If no identity is present, we redirect the redirect
configuration value.

If the user is authenticated, we continue the execution of the next middleware,
storing the identity in a request attribute. This facilitates consumption of the
identity information in subsequent middleware layers. For instance, imagine you
need to retrieve the user’s information:

Like the LoginActionFactory above, you could skip the factory creation and
instead use the ReflectionBasedAbstractFactory if using zend-servicemanager.

Require authentication for specific routes

Now that we built the authentication middleware, we can use it to protect
specific routes that require authentication. For instance, for each route that
needs authentication, we can modify the routing to create a pipeline that
incorporates our AuthAction middleware early:

The order of execution for the middleware is the order of the array elements.
Since the AuthAction middleware is provided as the first element, if a user is
not authenticated when requesting either the admin dashboard or config page,
they will be immediately redirected to the login page instead.

Conclusion

There are many ways to accommodate authentication within middleware
applications; this is just one. Our goal was to demonstrate the ease with which
you may compose authentication into existing workflows by creating middleware
that intercepts the request early within a pipeline.

You could certainly make a number of improvements to the workflow:

The path to the login page could be configurable.

You could capture the original request path in order to allow redirecting to
it following successful login.

You could introduce rate limiting of login requests.

These are each interesting exercises for you to try!

As noted in the introduction, this article demonstrates only authentication.
Stay tuned for a future article that will demonstrate authorization middleware
using zend-permissions-rbac.