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() !== 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 😉

9 Responses

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

  2. […] I wrote a post about Post/Redirect/Get in Expressive 3 which specifically handle POST parsed body. How about File upload? Let’s do […]

  3. MichaelB said, on August 28, 2018 at 9:26 pm

    Hi, i implemented your middleware (PrgMiddleware) and it works fine. But it create 2 or 3 different session id at login. The first still empty and is not used (may be not link with this middleware). The second contain the csrf and post_data values and when the user is authenticated (302 redirect just after 303), the second session still contain the post_data and a new session id is created with the Zend\Expressive\Authentication\UserInterface datas. At the final i have 3 session id.. One never used, second with post_data and the third with UserInterface data. Only the last one is used. Can you please check if this behavior is correct? My problem is that the second session with post_data contain credentials (what was posted from the login page) and are not erased after login… Thanks!

    • samsonasik said, on September 2, 2018 at 12:07 am

      you probably have error in the middle of process, and somehow error skipped, and the post_data session is not cleared, while it is already authenticated.

      • MichaelB said, on September 6, 2018 at 3:48 pm

        I tried to see some error but found nothing.. I have a simple login process (same that in your example). All works but an additional session id is generated at login..

        Just a question: AuthenticationMiddleware is in last position (in route). But in the process function of this middleware it call return $handler->handle($request->withAttribute(UserInterface::class, $user)); So what is executed next in this case? i mean, it have nothing after this class in route.. Thanks!

      • samsonasik said, on September 7, 2018 at 3:31 am

        I created repository for it at https://github.com/samsonasik/expressive3-example-auth-with-prg so you check, it has auth, csrf, flash, and prg usage.

        No, next position definition in route will be callable via `$handler->handle($request)` inside `LoginPageHandler::process()`.

      • MichaelB said, on July 16, 2019 at 4:41 pm

        Hi again, i check again my code and installed also your expressive3-example-auth-with-prg from github and it is the same result.

        Can you tell me why when i entered in the login page it create 2 session files (1 empty, 1 with csfr).
        And when i log in (post), 3 sessions files exists.. (1 empty, 1 with csrf with posted datas.. and a new one with Zend\Expressive\Authentication\UserInterface datas)

        I ran it from cli (php composer.phar run –timeout=0 serve) but same result in apache production server.

        Why 3 different session files for 1 user? Is it a normal behaviour?

        You can test it like that: I removed all sess_ files in /var/lib/php/sessions and clean cookies in Firefox. Go to the login page, check the php session folder, log in, check again the session folder.

        Thanks for your help.

      • samsonasik said, on July 20, 2019 at 9:48 am

        Hi MichaelB, It seems it was a expressive components issue. I updated components to use :

               "zendframework/zend-expressive-csrf": "^1.0.0",
                "zendframework/zend-expressive-flash": "^1.0.0",
        

        and run :

        composer update
        

        And now it only create 2 session files. I’ve updated repository https://github.com/samsonasik/expressive3-example-auth-with-prg with latest components

  4. Tigerman55 said, on March 29, 2019 at 2:32 am

    Hey. I tried this and was able to get it working properly. One useful thing I did was put it in my pipeline.php right under the SessionMiddleware, that way every post request will prg. It also means I don’t have to place the prg middleware in every request I need it.


Leave a Reply to samsonasik Cancel 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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s

%d bloggers like this: