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 before LoginPageHandler:

// 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');

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() !== 301) {
                    $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 ๐Ÿ˜‰

Advertisements

One Response

Subscribe to comments with RSS.

  1. […] 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 […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: