Update: I created a diagram to show the usage of RFC authentication and RFC authorization.
RFC: authentication module for Expressive and PSR-7 apps
Following the RFC for Authorization Middleware, I now propose an authentication module for middleware applications.
Goal
An authentication middleware checks user credentials provided in the HTTP request and blocks the pipeline execution on authentication failure.
The goal of this RFC is to build a general authentication module for Expressive and PSR-7 applications. The idea is to authenticate user credentials based on username and password or using a token (e.g. using OAuth2).
Requirements
The authentication middleware should be able to check against different authentication mechanisms using specific adapters (e.g. HTTP Basic/Digest Authentication, PHP Session, OAuth2, etc.).
The users should be managed using adapters, to be able to retrieve credential via different data storage mechanisms (e.g. htpasswd file, PDO-backed database, etc.).
Implementation
I proposed an implementation of the authentication middleware using the following adapter interface:
namespace Zend\Expressive\Authentication;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
interface AuthenticationInterface
{
/**
* Authenticate the PSR-7 request and return a valid user
* or null if not authenticated.
*/
public function authenticate(ServerRequestInterface $request) : ?UserInterface;
/**
* Generate the unauthorized response
*/
public function unauthorizedResponse(ServerRequestInterface $request) : ResponseInterface;
}
This interface defines 2 methods authenticate()
, and unauthorizedResponse()
. The first is used to authenticate a PSR-7 request, returning a UserInterface
object or null if not authenticated. The second function is used to generate the unauthorized response.
For instance, using Basic Access Authentication, the authentication is provided using an Authorization
header as follows:
Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l
In situations where the HTTP request does not contain this header, authentication must respond with a 401 Unauthorized
status with the following header:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="User Visible Realm"
Where the realm is a string that defines the protection space, in case of multiple authentication systems on the same server resource.
In the case of a valid credential, the authenticate()
method returns a UserInterface
instance. This is a simple interface described as follows:
namespace Zend\Expressive\Authentication;
interface UserInterface
{
/**
* Get the username
*/
public function getUsername() : string;
/**
* Get the user role
*/
public function getUserRole() : string;
}
This interface defines 2 methods, one each for retrieving the username and the user role. This information can be consumed by Authorization middleware to check for authorized users to perform specific actions (e.g. see the zend-expressive-authorization proposal).
In order to use different user repositories for credentials (e.g., to perform lookups using different storage backends), I propose the following interface:
namespace Zend\Expressive\Authentication;
interface UserRepositoryInterface
{
/**
* Authenticate the credential (username) using a password
* or using only the credential string (e.g. token based credential)
* It returns the authenticated user or null.
*
* @param string $credential Can be also a token
* @param string $password
* @return UserInterface|null
*/
public function authenticate(string $credential, string $password = null) : ?UserInterface;
}
This interface contains only one function, authenticate. This function can be used to check if the username (credential
) and password
are valid or not. The password
argument is optional, in case we need to authenticate against a single token string, specified by the credential
parameter.
If the user is authenticated it will return a UserInterface
object or null
if not authenticated.
Using these level of abstractions we should be able to manage all authentication mechanisms using different user repositories.
Configuration
To configure the proposed authentication middleware we can use alias services. We can use Zend\Expressive\Authentication\AuthenticationInterface::class
name for the authentication adapter and Zend\Expressive\Authentication\UserRepositoryInterface::class
for the user register adapter.
The default configuration provided by a ConfigProvider
class for the proposed module might look like the following:
namespace Zend\Expressive\Authentication;
class ConfigProvider
{
/**
* Return the configuration array.
*/
public function __invoke() : array
{
return [
'dependencies' => $this->getDependencies(),
'authentication' => include __DIR__ . '/../config/authentication.php',
];
}
/**
* Returns the container dependencies
*/
public function getDependencies() : array
{
return [
'aliases' => [
// Change the alias value for Authentication adapter and
// UserRepository adapter
AuthenticationInterface::class => Adapter\BasicAccess::class,
UserRepositoryInterface::class => UserRepository\Htpasswd::class,
],
'factories' => [
AuthenticationMiddleware::class => AuthenticationMiddlewareFactory::class,
Adapter\BasicAccess::class => Adapter\BasicAccessFactory::class,
UserRepository\Htpasswd::class => UserRepository\HtpasswdFactory::class,
UserRepository\PdoDatabase::class => UserRepository\PdoDatabaseFactory::class
],
];
}
}
The config/authentication.php file contains specific configuration used by the adapters.
I have prototyped an implementation of the authentication middleware in ezimuel/zend-expressive-authentication. Currently, I only implement HTTP Basic Authentication using 2 user repositories: htpasswd file or PDO database.