How Do You Use CSRF Tokens in a Mezzio Application?

How Do You Use CSRF Tokens in a Mezzio Application?

No matter how small your web app may be, security is essential! In this tutorial, you’ll learn how to add a CSRF token in forms used in Mezzio-based applications, to prevent attackers from being able to force your users to execute malicious actions.


Before we dive on in if you’re not sure with what CSRF (Cross-Site Request Forgery) is, here’s how OWASP (The Open Web Application Security Project) defines it:

CSRF is an attack that forces an end user to execute unwanted actions on a web application in which they’re currently authenticated. With a little help of social engineering (such as sending a link via email or chat), an attacker may trick the users of a web application into executing actions of the attacker’s choosing. If the victim is a normal user, a successful CSRF attack can force the user to perform state-changing requests like transferring funds, changing their email address, and so forth. If the victim is an administrative account, CSRF can compromise the entire web application.

To defend against this kind of attack, a CSRF token (also known as an Anti-CSRF token) is included in all legitimate form submissions sent to a server. After a form submission is received by the server, as part of validating the submission, the server checks the value of the CSRF token against the string that was generated when the form was first created.

If the two values are the same, you can be sure that the request is legitimate. If they’re different, or if the token is missing, the request is rejected because it’s likely that some malicious actor has either intercepted the request and stripped the token, or has tricked a user into making a spoofed request.

Every time a form is created by the server, the CSRF token is created anew. Secondly, often, though not always, whenever a CSRF token is validated, a new one is created.

Let’s assume that you have an existing form on your site, perhaps like this one on the landing page that I created for the Mezzio Essentials book. The user sees a small, perfectly normal-looking form, with only the fields that they’re required to fill in.

However, if you look at the form’s source, then you’ll see that it contains a hidden field named __csrf. This field contains the CSRF token. I’ve stripped out any unnecessary form content so that just the essential details are displayed.

<form action="/" method="post">
    <input type="email"
           name="email"
           placeholder="Your email address">
    <button type="submit">Download Your Free Sample</button>
    <input type="hidden"
           name="__csrf"
           value="ce6455d482dabb54da1e34f83fbacdefghi">
</form>

So, how do you use CSRF tokens in Mezzio?

Conceptually, there isn’t that much to do. Let’s step through the process of implementing it in Mezzio so that you learn how to do it.

The first thing that you need to do is to ensure that your configuration of PHP has the Sessions extension installed and enabled. By default it is, but depending on your PHP installation, cloud, or hosting provider, it might not be. Always helps to be sure.

To check, run the following command. If you see “session” output to the console, then it’s installed and enabled.

grep session <(php -m)

Note: There are other ways to persist the CSRF token, but I’ll cover those in future posts.

Install the required packages

The next thing to do is to add the 3 required packages:

  • mezzio/mezzio-csrf
  • mezzio/mezzio-session
  • mezzio/mezzio-session-ext

To do that, run the following command in the terminal in the root directory of your project:

composer require \
    mezzio/mezzio-csrf \
    mezzio/mezzio-session \
    mezzio/mezzio-session-ext

During the installation, you’ll see the following question:

Please select which config file you wish to inject 'Mezzio\Session\ConfigProvider' into:
[0] Do not inject
[1] config/config.php
Make your selection (default is 1):

Accept the default value and continue with the package installation. Then, when it asks the following question, accept the default of Y as well.

Remember this option for other packages of the same type? (Y/n)

Enable the required middleware

With those packages installed, we need to make use two pieces of middleware from them: SessionMiddleware from mezzio-session, and CsrfMiddleware from mezzio-csrf.

SessionMiddleware
This middleware composes a persistence instance and uses that to generate a session container, which it pushes into the request it delegates to the next middleware. Once a response is returned, it uses the persistence instance to persist the session data and provide information back to the client.
CsrfMiddleware
Since CSRF token generation and validation rely on request artifacts, we use Mezzio\Csrf\CsrfMiddleware to generate the appropriate guard instance and pass it into a request attribute. If this is your first time hearing the term “guard”, it’s:

when you generated a token server-side and pass it down to the page so that client-side requests submitting forms can pass it back to verify the request is legit. - David McKay (@rawkode)

We need to load the two Middleware classes for the form’s route — in the correct order. SessionMiddleware has to be loaded first, so that requests have a persistence layer to store generated CSRF tokens. CsrfMiddleware has to be loaded second, and provides the functionality for generating and validating CSRF tokens.

To do that, assuming that you have defined your routes in config/routes.php, update the route’s handler to be an array, as in the highlighted example below.

$app->route(
    '/',
    [
        \Mezzio\Session\SessionMiddleware::class,
        \Mezzio\Csrf\CsrfMiddleware::class,
        \App\Handler\HomePageHandler::class
    ],
    ['post', 'get'],
    'home'
);

The example above assumes that you only need them for one route. If you need to have them available for every route in your application, add the following code to config/pipeline.php:

$app->pipe(\Mezzio\Session\SessionMiddleware::class);
$app->pipe(\Mezzio\Router\Middleware\RouteMiddleware::class);

TIP: Register them before RouteMiddleware is registered.

Refactor the route’s handler code to be CSRF-aware

With the Middleware correctly loaded for the route, it’s time to add the required code in the route’s handler.

The handler that you put it in is up to your discretion. For this article, let’s assume that it is in the default handler that comes with applications generated with the Mezzio Skeleton, src/App/Handler/HomePageHandler.php.

public function handle(ServerRequestInterface $request)
  : ResponseInterface
{
    $data = [];
    $form = (new AnnotationBuilder())
              ->createForm(PreviewBookDownload::class);

    /** @var CsrfGuardInterface $guard */
    $guard = $request->getAttribute(
        CsrfMiddleware::GUARD_ATTRIBUTE
    );

    if ($request->getMethod() === 'GET') {
        $data['__csrf'] = $guard->generateToken();
    }

    if ($request->getMethod() === 'POST') {
        $data = $request->getParsedBody();
        $form->setData($data);
        $token = $data['__csrf'] ?? '';
        if ($guard->validateToken($token) && $form->isValid()) {
            // Handle a positive form submission
            // ...
        }
    }

    return new HtmlResponse(
        $this->template->render(
            'app::home-page', $data
        )
    );
}

Make sure you include the required use statements:

use App\Entity\PreviewBookDownload;
use Laminas\Form\Annotation\AnnotationBuilder;
use Mezzio\Csrf\CsrfGuardInterface;
use Mezzio\Csrf\CsrfMiddleware;

The method first instantiates a Laminas Form object using Laminas Form’s AnnotationBuilder.

That it instantiates the form object in this way isn’t important. It’s just the approach that I took in this scenario, as I like the functionality which Laminas Form provides for handling request filtering and validation.

Tip: If you want to use this approach, you’ll need to run the following command:

composer require \
    laminas/laminas-form \
    doctrine/common

It then retrieves the CSRF guard, by retrieving the CsrfMiddleware::GUARD_ATTRIBUTE attribute from the request, put there by CsrfMiddleware, and uses it to initialise a new variable, aptly named $guard.

It then checks if the HTTP request method was GET. If so, it calls $guard’s generateToken method to generate a new CSRF token and initialises the __csrf element of the $data array with it.

If the request method was POST, it retrieves the body of the request, containing the form data, by calling $request’s getParsedBody(), and stores the retrieved array in $data. After doing that, it then passes $data to a call to $form’s setData method, setting the value of the form’s fields with the values supplied in the request body.

After that, it attempts to retrieve the token from $data’s __csrf element, and initialise a new variable, named $token with it. If the element is not set or is empty, then $token is set to an empty string.

Following that, it attempts to validate the token, by calling $guard’s validateToken method. It also runs the form’s validation rules on the remainder of the form’s data.

If both method calls succeed, then the remaining code is executed. If either fails, then the form is displayed again along with an appropriate error message. I’ve not displayed this specific code, as it’s not essential to the understanding of integrating CSRF tokens into a Mezzio application.

After that, there’s one final aspect of code to cover, which is the returned HtmlResponse object. This renders the page’s template, after rendering the contents of $data into it.

If it’s a GET request, then an empty form, containing the CSRF token will be displayed on the page. If it’s a POST request, then the form will be rendered, with the CSRF token and any applicable error messages.

Update the form’s view template

I’m not going to cover the handler’s view template in great depth, as there isn’t much to cover. It’s a standard laminas-view view script. If you’re not familiar with laminas-view, I strongly encourage you to read the laminas-view Quick Start, which thoroughly covers them.

Here is the key line from the template file:

<form action="/" method="post">
    <div id="errors">
        <b>Please correct the following error(s):</b>
        <ul class="mt-2 list-inside list-disc text-gray-900">
            <li v-for="error in errors">{{ error }}</li>
        </ul>
    </div>
    <div class="mt-6">
        <label>
            <input type="email"
                   name="email"
                   id="email"
                   autocapitalize="off"
                   autocomplete="off"
                   autocorrect="off"
                   placeholder="Your email address">
        </label>
    </div>
    <div class="mt-3">
        <button type="submit">Submit</button>
    </div>
    <input type="hidden"
        name="__csrf"
        id="__csrf"
        value="<?= $this->__csrf; ?>">
</form>

When the form is rendered, because of the PHP code in the value element, the form’s value will be set to the CSRF token.

That’s how to use a CSRF token in a Mezzio application

And that’s the essentials of integrating CSRF tokens into a Mezzio-based application. We installed the required packages and saw how to enable them for a specific route, as well as globally for all routes.

We refactored a handler’s handle method to generate and validate a token based on whether the request method was GET or POST. Finally, we saw how to update the view’s template to render the CSRF token in the __csrf field.

Sure, there are many aspects of functionality that contribute to making it work. However, I hope that the article’s identified what they are and shown you how they fit together.

That said, while using a CSRF token is a solid additional measure of security, more steps can and should be taken to fully secure your forms. Check out https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html[OWASP's Cross-Site Request Forgery Prevention Cheat Sheet] if you’d like to learn more.

Finally, security is a fun subject and something that we all need to do to ensure that malicious actors don’t take advantage of our users. Sure, it’s more work for us, but it’s a fantastic opportunity to learn, to grow our skills and help our users have a better experience with the apps that we create.

Non fungible token icons created by Muhammad Ali - Flaticon Non fungible token icons created by Vitaly Gorbachev - Flaticon

Want to Learn More About Mezzio?

Mezzio Essentials teaches you the fundamentals of PHP's Mezzio framework. It's a practical, hands-on approach, which shows you just enough of about the underlying principles and concepts before stepping you through the process of creating an application.

You might also be interested in these tutorials too...

Enable Mezzio Modules with laminas-component-installer
Mon, Dec 16, 2024

Enable Mezzio Modules with laminas-component-installer

When building reusable Mezzio packages, such as for user management, payments, and authentication, do users have to enable them manually, or are you automating it for them? In this short tutorial, I’ll show you how to enable them almost automatically, saving your users time and effort.

How to Manually Create a Mezzio Application
Thu, Aug 13, 2020

How to Manually Create a Mezzio Application

If you want to build applications in PHP — from one-page apps to enterprise-grade applications — PHP’s Mezzio framework is a excellent framework to choose. In this tutorial, which is an excerpt from my new book, Mezzio Essentials, I’ll show you how to manually build an application in Mezzio.


Want more tutorials like this?

If so, enter your email address in the field below and click subscribe.

You can unsubscribe at any time by clicking the link in the footer of the emails you'll receive. Here's my privacy policy, if you'd like to know more. I use Mailchimp to send emails. You can learn more about their privacy practices here.

Join the discussion

comments powered by Disqus