To provide CSRF protection, we provide an abstraction,
Mezzio\Csrf\CsrfGuardInterface
:
namespace Mezzio\Csrf;
interface CsrfGuardInterface
{
/**
* Generate a CSRF token.
*
* Typically, implementations should generate a one-time CSRF token,
* store it within the session, and return it so that developers may
* then inject it in a form, a response header, etc.
*
* CSRF tokens should EXPIRE after the first hop.
*/
public function generateToken(string $keyName = '__csrf') : string;
/**
* Validate whether a submitted CSRF token is the same as the one stored in
* the session.
*
* CSRF tokens should EXPIRE after the first hop.
*/
public function validateToken(string $token, string $csrfKey = '__csrf') : bool;
}
Because guards will be backed by different mechanisms, we provide CsrfMiddleware that will generate the guard based on configuration, and inject it into the request passed to later middleware; this approach allows you to separate generation from the guard instance (which is based on request data) from your own middleware.
Once you have a concrete implementation, you will generally:
- Generate a token in middleware displaying a form, and
- Validate a token in middleware validating that form.
As an example, we could have middleware displaying a form as follows:
namespace Books;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Laminas\Diactoros\Response\HtmlResponse;
use Mezzio\Csrf\CsrfGuardInterface;
use Mezzio\Csrf\CsrfMiddleware;
use Mezzio\Template\TemplateRendererInterface;
class DisplayBookFormHandler implements MiddlewareInterface
{
private $renderer;
public function __construct(TemplateRendererInterface $renderer)
{
$this->renderer = $renderer;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
{
$guard = $request->getAttribute(CsrfMiddleware::GUARD_ATTRIBUTE);
$token = $guard->generateToken();
return new HtmlResponse(
$this->renderer->render('books::form', [
'__csrf' => $token,
]);
);
}
}
When we're ready to process it, we then might have the following middleware:
namespace Books;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Laminas\Diactoros\Response\EmptyResponse;
use Laminas\Diactoros\Response\HtmlResponse;
use Mezzio\Csrf\CsrfGuardInterface;
use Mezzio\Csrf\CsrfMiddleware;
use Mezzio\Template\TemplateRendererInterface;
class ProcessBookFormHandler implements MiddlewareInterface
{
private $renderer;
public function __construct(TemplateRendererInterface $renderer)
{
$this->renderer = $renderer;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
{
$guard = $request->getAttribute(CsrfMiddleware::GUARD_ATTRIBUTE);
$data = $request->getParsedBody();
$token = $data['__csrf'] ?? '';
if (! $guard->validateToken($token)) {
return new EmptyResponse(412); // Precondition failed
}
// process form normally and return a response...
}
}
This approach allows you to prevent CSRF attacks separately from normal form validation, which can also simplify how your forms are structured.
We provide two guard implementations, one using the base session container functionality from mezzio-session, and another using flash messages as provided in mezzio-flash.
Session-based guards are provided via Mezzio\Csrf\SessionCsrfGuard
.
This class expects a Mezzio\Session\SessionInterface
instance to its
constructor, and it then uses that to both store a token in the session during
generateToken()
, and when validating a submitted token.
Flash guards are provided via Mezzio\Csrf\FlashCsrfGuard
. This class
expects a Mezzio\Flash\FlashMessagesInterface
instance to its
constructor, and it then uses that to store a token via a flash message when
generateToken()
is called, and to retrieve a previously flashed token when
validating a submitted token.
To use this guard, you will also need to install the mezzio-flash package:
$ composer require mezzio/mezzio-flash
Because guard implementations generally require request-based artifacts in order to do their work, we provide an interface describing a factory for generating guards. Essentially, each guard implementation will also supply their own factory implementation, which the CsrfMiddleware will then consume to create a guard instance.
Mezzio\Csrf\CsrfGuardFactoryInterface
defines the following:
namespace Mezzio\Csrf;
use Psr\Http\Message\ServerRequestInterface;
interface CsrfGuardFactoryInterface
{
public function createGuardFromRequest(ServerRequestInterface $request) : CsrfGuardInterface;
}
We provide the following concrete factories:
Mezzio\Csrf\SessionCsrfGuardFactory
Mezzio\Csrf\FlashCsrfGuardFactory
You will need to map the appropriate one to the
Mezzio\Csrf\CsrfGuardFactoryInterface
service in your dependency
injection container. By default, we map this service to the
SessionCsrfGuardFactory
.
You may also compose the CsrfGuardFactoryInterface
directly in your own
middleware. When you do, you will have to manually use it to create the guard
instance prior to generating or validating a token:
class SomeHandler implements MiddlewareInterface
{
private $guardFactory;
public function __construct(CsrfGuardFactoryInterface $guardFactory)
{
$this->guardFactory = $guardFactory;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
{
$guard = $this->guardFactory->createGuardFromRequest($request);
// ...
}
}