One very important usage of middleware is to provide authentication. Most web-based applications need the ability to verify a visitor via username and password. By incorporating PSR-7 standards into an authentication class, you will make it generically useful across the board, so to speak, being secure enough that it can be used in any framework that provides PSR-7-compliant request and response objects.
Application\Acl\AuthenticateInterface class. We use this interface to support the Adapter software design pattern, making our Authenticate class more generically useful by allowing a variety of adapters, each of which can draw authentication from a different source (for example, from a file, using OAuth2, and so on). Note the use of the PHP 7 ability to define the return value data type:namespace Application\Acl;
use Psr\Http\Message\ { RequestInterface, ResponseInterface };
interface AuthenticateInterface
{
public function login(RequestInterface $request) :
ResponseInterface;
}login() method required by the interface. We make sure to use the appropriate classes, and define fitting constants and properties. The constructor makes use of Application\Database\Connection, defined in Chapter 5, Interacting with a Database:namespace Application\Acl;
use PDO;
use Application\Database\Connection;
use Psr\Http\Message\ { RequestInterface, ResponseInterface };
use Application\MiddleWare\ { Response, TextStream };
class DbTable implements AuthenticateInterface
{
const ERROR_AUTH = 'ERROR: authentication error';
protected $conn;
protected $table;
public function __construct(Connection $conn, $tableName)
{
$this->conn = $conn;
$this->table = $tableName;
}login() method extracts the username and password from the request object. We then do a straightforward database lookup. If there is a match, we store user information in the response body, JSON-encoded:public function login(RequestInterface $request) :
ResponseInterface
{
$code = 401;
$info = FALSE;
$body = new TextStream(self::ERROR_AUTH);
$params = json_decode($request->getBody()->getContents());
$response = new Response();
$username = $params->username ?? FALSE;
if ($username) {
$sql = 'SELECT * FROM ' . $this->table
. ' WHERE email = ?';
$stmt = $this->conn->pdo->prepare($sql);
$stmt->execute([$username]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row) {
if (password_verify($params->password,
$row['password'])) {
unset($row['password']);
$body =
new TextStream(json_encode($row));
$response->withBody($body);
$code = 202;
$info = $row;
}
}
}
return $response->withBody($body)->withStatus($code);
}
}Authenticate class is a wrapper for an adapter class that implements AuthenticationInterface. Accordingly, the constructor takes an adapter class as an argument, as well as a string that serves as the key, in which authentication information is stored in $_SESSION:namespace Application\Acl;
use Application\MiddleWare\ { Response, TextStream };
use Psr\Http\Message\ { RequestInterface, ResponseInterface };
class Authenticate
{
const ERROR_AUTH = 'ERROR: invalid token';
const DEFAULT_KEY = 'auth';
protected $adapter;
protected $token;
public function __construct(
AuthenticateInterface $adapter, $key)
{
$this->key = $key;
$this->adapter = $adapter;
}public function getToken()
{
$this->token = bin2hex(random_bytes(16));
$_SESSION['token'] = $this->token;
return $this->token;
}
public function matchToken($token)
{
$sessToken = $_SESSION['token'] ?? date('Ymd');
return ($token == $sessToken);
}
public function getLoginForm($action = NULL)
{
$action = ($action) ? 'action="' . $action . '" ' : '';
$output = '<form method="post" ' . $action . '>';
$output .= '<table><tr><th>Username</th><td>';
$output .= '<input type="text" name="username" /></td>';
$output .= '</tr><tr><th>Password</th><td>';
$output .= '<input type="password" name="password" />';
$output .= '</td></tr><tr><th> </th>';
$output .= '<td><input type="submit" /></td>';
$output .= '</tr></table>';
$output .= '<input type="hidden" name="token" value="';
$output .= $this->getToken() . '" />';
$output .= '</form>';
return $output;
}login() method in this class checks whether the token is valid. If not, a 400 response is returned. Otherwise, the login() method of the adapter is called:public function login(
RequestInterface $request) : ResponseInterface
{
$params = json_decode($request->getBody()->getContents());
$token = $params->token ?? FALSE;
if (!($token && $this->matchToken($token))) {
$code = 400;
$body = new TextStream(self::ERROR_AUTH);
$response = new Response($code, $body);
} else {
$response = $this->adapter->login($request);
}
if ($response->getStatusCode() >= 200
&& $response->getStatusCode() < 300) {
$_SESSION[$this->key] =
json_decode($response->getBody()->getContents());
} else {
$_SESSION[$this->key] = NULL;
}
return $response;
}
}First of all, be sure to follow the recipes defined in Appendix, Defining PSR-7 Classes. Next, go ahead and define the classes presented in this recipe, summarized in the following table:
|
Class |
Discussed in these steps |
|---|---|
|
|
1 |
|
|
2 - 3 |
|
|
4 - 6 |
You can then define a chap_09_middleware_authenticate.php calling program that sets up autoloading and uses the appropriate classes:
<?php
session_start();
define('DB_CONFIG_FILE', __DIR__ . '/../config/db.config.php');
define('DB_TABLE', 'customer_09');
define('SESSION_KEY', 'auth');
require __DIR__ . '/../Application/Autoload/Loader.php';
Application\Autoload\Loader::init(__DIR__ . '/..');
use Application\Database\Connection;
use Application\Acl\ { DbTable, Authenticate };
use Application\MiddleWare\ { ServerRequest, Request, Constants, TextStream };You are now in a position to set up the authentication adapter and core class:
$conn = new Connection(include DB_CONFIG_FILE); $dbAuth = new DbTable($conn, DB_TABLE); $auth = new Authenticate($dbAuth, SESSION_KEY);
Be sure to initialize the incoming request, and set up the request to be made to the authentication class:
$incoming = new ServerRequest(); $incoming->initialize(); $outbound = new Request();
Check the incoming class method to see if it is POST. If so, pass a request to the authentication class:
if ($incoming->getMethod() == Constants::METHOD_POST) {
$body = new TextStream(json_encode(
$incoming->getParsedBody()));
$response = $auth->login($outbound->withBody($body));
}
$action = $incoming->getServerParams()['PHP_SELF'];
?>The display logic looks like this:
<?= $auth->getLoginForm($action) ?>
Here is the output from an invalid authentication attempt. Notice the 401 status code on the right. In this illustration, you could add a var_dump() of the response object:

Here is a successful authentication:

For guidance on how to avoid CSRF and other attacks, please see Chapter 12, Improving Web Security.