Welcome to Abdul Malik Ikhsan's Blog

Create Middleware for Post/Redirect/Get in Expressive 3

Posted in expressive, Zend Framework by samsonasik on February 13, 2018

Yesterday, we already explore about CSRF usage in Expressive 3 using zend-expressive-csrf component, which I gave the sample inside a Login Page which utilize both GET and POST in single page, which we need to tweak the csrf regeneration to ensure next retry will use newly regenerated token. As I noted in the last paragraph, the better usage is by using PRG ( Post/Redirect/Get ) or delegate to separate middleware and redirect back to its page. Today, we will explore how to create Post/Redirect/Get Middleware for Expressive 3.

Requirements

If you already followed my 4 previous expressive posts, all requirements already applied.

The New Middleware

We are going to create a middleware for our application, for example, we name it PrgMiddleware, placed at src/App/Middleware. I will explain part by part.

First, we check whether the request method is POST, then save the POST data into session with new key, eg: ‘post_data’, then redirect to current page with status code = 303.

use Zend\Diactoros\Response\RedirectResponse;
use Zend\Expressive\Session\SessionMiddleware;
// ...
    $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
    if ($request->getMethod() === 'POST') {
        $session->set('post_data', $request->getParsedBody());
        return new RedirectResponse($request->getUri(), 303);
    }
    // ...

On next flow, we can check if the session has ‘post_data’ key:

    if ($session->has('post_data')) {
        $post = $session->get('post_data');
        $session->unset('post_data');

        $request = $request->withMethod('POST');
        $request = $request->withParsedBody($post);
    }
    // ...

As in authentication process, we use Zend\Expressive\Authentication\Session\PhpSession::authenticate() which require request method to be POST and use its parsed body to be used for authentication, we need to set method to POST with parsed body of saved session with key ‘post_data’ which immediately removed above.

Lastly, we return the $handler->handle($request) as whenever response it used:

    return $handler->handle($request);

The complete middleware class can be as follows:

<?php
// src/App/Middleware/PrgMiddleware.php
declare(strict_types=1);

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\RedirectResponse;
use Zend\Expressive\Session\SessionMiddleware;

class PrgMiddleware implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);

        if ($request->getMethod() === 'POST') {
            $session->set('post_data', $request->getParsedBody());
            return new RedirectResponse($request->getUri(), 303);
        }

        if ($session->has('post_data')) {
            $post = $session->get('post_data');
            $session->unset('post_data');

            $request = $request->withMethod('POST');
            $request = $request->withParsedBody($post);
        }

        return $handler->handle($request);
    }
}

PrgMiddleware Service Registration

We can register the PrgMiddleware at src/App/ConfigProvider under getDependencies() function:

// src/App/ConfigProvider.php
use Zend\ServiceManager\Factory\InvokableFactory;

    // ...
    public function getDependencies() : array
    {
        return [
            'factories'  => [
                // ...
                Middleware\PrgMiddleware::class => InvokableFactory::class,
            ],
        ];
    }

Now, we can register it inside config/routes.php for login route after csrf middleware registrations:

// config/routes.php
$app->route('/login', [
    // csrf
    \Zend\Expressive\Csrf\CsrfMiddleware::class,

    // prg middleware
    App\Middleware\PrgMiddleware::class,

    // login page
    App\Handler\LoginPageHandler::class,

    // authentication middleware
    \Zend\Expressive\Authentication\AuthenticationMiddleware::class,
], ['GET', 'POST'],'login');

To make clear “post_data” executed, place the App\Middleware\PrgMiddleware::class at the very first in target redirect, eg: home route:

    $app->get('/', [
        // prg handling
        App\Middleware\PrgMiddleware::class,

        \Zend\Expressive\Authentication\AuthenticationMiddleware::class,
        App\Handler\HomePageHandler::class,
    ], 'home');

Using The Prg in LoginPageHandler

We can check if it is a PRG using the parsed body and use it to fill the form data:

// src/App/Handler/LoginPageHandler.php
    // ...
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        $guard     = $request->getAttribute(CsrfMiddleware::GUARD_ATTRIBUTE);
        $loginForm = new LoginForm($guard);

        $prg = $request->getParsedBody();
        if ($prg) {
            $loginForm->setData($prg);
            if ($loginForm->isValid()) {
                // ...
            }
        }
    }

By above, we just need to generate the token once. The complete LoginPageHandler can be as follow:

<?php
// src/App/Handler/LoginPageHandler.php
declare(strict_types=1);

namespace App\Handler;

use App\Form\LoginForm;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\RedirectResponse;
use Zend\Expressive\Csrf\CsrfMiddleware;
use Zend\Expressive\Flash\FlashMessageMiddleware;
use Zend\Expressive\Session\SessionMiddleware;
use Zend\Expressive\Template\TemplateRendererInterface;

class LoginPageHandler implements MiddlewareInterface
{
    private $template;

    public function __construct(TemplateRendererInterface $template)
    {
        $this->template  = $template;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        $guard     = $request->getAttribute(CsrfMiddleware::GUARD_ATTRIBUTE);
        $loginForm = new LoginForm($guard);

        $prg = $request->getParsedBody();
        if ($prg) {
            $loginForm->setData($prg);
            if ($loginForm->isValid()) {
                $response = $handler->handle($request);

                $flashMessages = $request->getAttribute(FlashMessageMiddleware::FLASH_ATTRIBUTE);
                if ($response->getStatusCode() !== 302) {
                    $flashMessages->flash('message', 'You are succesfully authenticated');
                    return new RedirectResponse('/');
                }

                $flashMessages->flash('message', 'Login Failure, please try again');
                return new RedirectResponse('/login');
            }
        }

        $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
        $token   = $guard->generateToken();

        return new HtmlResponse(
            $this->template->render('app::login-page', [
                'form'  => $loginForm,
                'token' => $token,
            ])
        );
    }
}

In view, we can just show the form:

<?php // templates/app/login-page.phtml

$form->get('csrf')->setValue($token);
$form->prepare();
echo $this->form($form);

Done 😉

Using zend-expressive-csrf with zend-form in Expressive 3

Posted in expressive, Tutorial PHP, Zend Framework by samsonasik on February 13, 2018

If you followed my post about authentication and authorization posts with Expressive 3, this time, I write another session related post for securing request, which uses zend-expressive-csrf with zend-form.

Setup

We already installed session and form components, so, we can just require the zend-expressive-csrf component via command:

$ composer require \
    zendframework/zend-expressive-csrf:^1.0.0alpha1

On Login route, we register the csrf middleware before the LoginPageHandler:

// config/routes.php
$app->route('/login', [
    // csrf middleware
    \Zend\Expressive\Csrf\CsrfMiddleware::class,

    App\Handler\LoginPageHandler::class,
    \Zend\Expressive\Authentication\AuthenticationMiddleware::class,
], ['GET', 'POST'],'login');

Csrf Validation in Form

We can inject the LoginForm with Zend\Expressive\Csrf\SessionCsrfGuard instance which next we use it to validate csrf token.

// src/App/Form/LoginForm.php
use Zend\Expressive\Csrf\SessionCsrfGuard;

    // ...
    private $guard;

    public function __construct(SessionCsrfGuard $guard)
    {
        parent::__construct('login-form');
        $this->guard = $guard;

        $this->init();
    }
    // ...

In above __construct(), I call init() immediatelly on __construct() to add form elements on form creation, as we are going to inject the Form with Zend\Expressive\Csrf\SessionCsrfGuard instance on LoginPageHandler which pulled from request object.

We then can add new element, for example, named: csrf as hidden input, as follows:

// src/App/Form/LoginForm.php
use Zend\Form\Element\Hidden;

    // ...
    public function init()
    {
        $this->add([
            'type'  => Hidden::class,
            'name'  => 'csrf',
        ]);
        //...
    }
    // ...

To validate it, we register the csrf validation token by Zend\Expressive\Csrf\SessionCsrfGuard in getInputFilterSpecification():

// src/App/Form/LoginForm.php
    // ...
    public function getInputFilterSpecification()
    {
        return [
            [
                'name' => 'csrf',
                'required' => true,
                'validators' => [
                    [
                        'name' => 'callback',
                        'options' => [
                            'callback' => function ($value) {
                                return $this->guard->validateToken($value);
                            },
                            'messages' => [
                                'callbackValue' => 'The form submitted did not originate from the expected site'
                            ],
                        ],
                    ]
                ],
            ],
            // ...
        ];
    }

Above, we supply a callback validator (you can create a special validator just for it) with call the Zend\Expressive\Csrf\SessionCsrfGuard::validateToken($value) which returns true when valid, and false when invalid. On invalid token, we will get callbackValue message key which we can customize its value.

The LoginPageHandler

As the Zend\Expressive\Csrf\SessionCsrfGuard instance will be injected at the LoginPageHandler itself, we can remove the LoginForm from LoginPageHandler dependency, so, we just need to have TemplateRendererInterface:

// src/Handler/LoginPageHandler.php
    // ...
    public function __construct(TemplateRendererInterface $template)
    {
        $this->template  = $template;
    }
    // ...

The factory for it will remove LoginForm dependency as well, so just the template:

// src/Handler/LoginPageFactory.php
    // ...
    public function __invoke(ContainerInterface $container) : MiddlewareInterface
    {
        $template  = $container->get(TemplateRendererInterface::class);
        return new LoginPageHandler($template);
    }

As the token csrf is a one time token, and we use a single page for both GET (show form) and POST (authenticate), we can create a function to get generated token to be called before POST method check, and when authentication failure or the form is invalid to ensure next retry will use newly generated token:

// src/Handler/LoginPageHandler.php
use Zend\Expressive\Session\SessionInterface;
use Zend\Expressive\Csrf\SessionCsrfGuard;

    // ...
    private function getToken(SessionInterface $session, SessionCsrfGuard $guard)
    {
        if (! $session->has('__csrf')) {
            return $guard->generateToken();
        }

        return $session->get('__csrf');
    }
    // ...

Now, the LoginForm and the token can be created during process() method:

// src/Handler/LoginPageHandler.php
use Zend\Expressive\Session\SessionMiddleware;
use Zend\Expressive\Csrf\CsrfMiddleware;
use Zend\Expressive\Csrf\SessionCsrfGuard;

    // ...
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        $guard     = $request->getAttribute(CsrfMiddleware::GUARD_ATTRIBUTE);
        $loginForm = new LoginForm($guard);

        $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
        $token   = $this->getToken($session, $guard);

        // ...
    }

On after form is valid check, the $token need to be re-generated:

// src/Handler/LoginPageHandler.php

    // ...
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        // ...
        if ($loginForm->isValid()) {
            // ...
        }

        // re-new token on failure login or not valid form
        $token   = $this->getToken($session, $guard);
    }

The $token will be consumed by view and can be set via template render:

// src/Handler/LoginPageHandler.php

    // ...
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        // ...
        return new HtmlResponse(
            $this->template->render('app::login-page', [
                'form'  => $loginForm,
                'error' => $error,
                'token' => $token,
            ])
        );
    }

The View

Finally, we can set the csrf element value in view via form object, as follows:

<?php // templates/app/login-page.phtml
echo $error;

$form->get('csrf')->setValue($token);
$form->prepare();

echo $this->form($form);

All done, now, when the request is not using the session generated token, it will show form error:

The form submitted did not originate from the expected site

like the following screenshot:

Better Practice and Possible Refactor

For real application, it is better to use PRG or use another handler to handle it to be redirected back to the form when failure, so you don’t need to tweak the token regeneration as when form re-displayed again, it already in next request and we can just use the new token. I’ve written new post for create middleware for Post/Redirect/Get in Expressive 3 for it.

Using View Helper for Accessing zend-expressive-flash Messages in Expressive 3

Posted in expressive, Zend Framework by samsonasik on February 11, 2018

If you already tried zend-expressive 3.0.0alpha3, and want to use flash message for it, you can use the new component: “zendframework/zend-expressive-flash”.

Setup

We need to require the component and its dependencies:

$ composer require
    zendframework/zend-expressive-session:^1.0.0 \
    zendframework/zend-expressive-session-ext:^1.0.0 \
    zendframework/zend-expressive-flash:^1.0.0

After it, we can ensure that the ConfigProvider for above components registered at config/config.php:

$aggregator = new ConfigAggregator([
    // ...
    \Zend\Expressive\Session\ConfigProvider::class,
    \Zend\Expressive\Session\Ext\ConfigProvider::class,
    \Zend\Expressive\Flash\ConfigProvider::class,
    // ...
]);

To make the flash messages available in all pages, we need to register it in the pipeline before RouteMiddleware piping:

use Zend\Expressive\Flash\FlashMessageMiddleware;
use Zend\Expressive\Session\SessionMiddleware;
// ...

    $app->pipe(SessionMiddleware::class);
    $app->pipe(FlashMessageMiddleware::class);

    $app->pipe(RouteMiddleware::class);

Get the Messages via View Helper

Most of the use cases, we are displaying the flash messages in the layout. If we are using zend-view for the template engine, we can create view helper to access the flash messages. What we need is a Zend\Expressive\Flash\FlashMessages instance with passes Zend\Expressive\Session\Session which passes $_SESSION into it if session status is active, as follows:

use Zend\Expressive\Session\Session;
use Zend\Expressive\Flash\FlashMessages;

// ...
$session = session_status() === PHP_SESSION_ACTIVE
     ? $_SESSION
     : [];

$flashMessages = FlashMessages::createFromSession(new Session($session));

The messages is a private property “currentMessages” which currently no public function that returns it. We can wait for the PR for it to be merge or use it via ReflectionProperty or Closure

which can be called by method:

$flashMessages->getFlashes();

Knowing that, we can create the view helper as follows:

<?php
// src/App/View\Helper\Flash.php

declare(strict_types=1);

namespace App\View\Helper;

use Zend\Expressive\Session\Session;
use Zend\Expressive\Flash\FlashMessages;
use Zend\View\Helper\AbstractHelper;

class Flash extends AbstractHelper
{
    public function __invoke() : array
    {
        $session = session_status() === PHP_SESSION_ACTIVE
            ? $_SESSION
            : [];

        $flashMessages = FlashMessages::createFromSession(
            new Session($session)
        );

        return $flashMessages->getFlashes();
    }
}

and register it into view_helpers config at src/App/ConfigProvider::__invoke():

// ...
    public function __invoke() : array
    {
        return [
            'dependencies' => $this->getDependencies(),
            'templates'    => $this->getTemplates(),

            // register additional view helpers
            'view_helpers' => [
                'invokables' => [
                    'flash' => View\Helper\Flash::class,
                ],
            ],
        ];
    }
// ...

Accessing Flash Messages in Layout

In layout, we can now access it via invoked flash view helper with loop the messages to be displayed:

<?php if ($flashes = $this->flash()) :?>
    <ul>
        <?php foreach($flashes as $message) : ?>
            <li><?php echo $message; ?></li>
        <?php endforeach; ?>
    </ul>
<?php endif; ?>

Give it a try

You can now create a flash messages in some page and access it in next request in the layout, check the documentation for its usage!