Robert Brax
Robert Brax

Reputation: 7318

Proper way to redirect from slim middleware

The goal is to redirect user to the login page from a middleware. The middleware is called from a slim route group with this line:

$this->get( '/profile', ProfileController::class . ':index' )->add( new RequireLogin() );

And here is the middleware

<?php

namespace Rib\Src\MiddleWares;


use Rib\Src\Services\FlashMessages;
use Slim\Http\Request;
use Slim\Http\Response;

class RequireLogin
{


    /**
     * Example middleware invokable class
     *
     * @param  \Psr\Http\Message\ServerRequestInterface $request PSR7 request
     * @param  \Psr\Http\Message\ResponseInterface $response PSR7 response
     * @param  callable $next Next middleware
     *
     * @return \Psr\Http\Message\ResponseInterface
     */
    public function __invoke( $request, $response, $next )
    {

        if ( ! isset( $_SESSION[ 'id' ] ) ) {
            FlashMessages::flashIt( 'message', "The page you tried to access requires an active session. Please log back in." );
            header( 'Location: /user/login' ); # THIS LINE
            exit; # AND THIS ONE
        }



        $response = $next( $request, $response );

        return $response;
    }

}

My goal is that when middleware detects that the user is not currently logged in, then redirect user to login page. You can see that my current way is BAD: I use a redirect followed by an exit.

How could I replace these 2 lines with the proper "slim" way ?

Upvotes: 0

Views: 2216

Answers (1)

Georgy Ivanov
Georgy Ivanov

Reputation: 1579

Response has a handy method withRedirect(), you can use it like this:

<?php

if (!isset($_SESSION[ 'id' ])) {
    FlashMessages::flashIt( 'message', "The page you tried to access requires an active session. Please log back in." );
    $url = '/user/login';
    return $response->withRedirect($url);
} else {
    return $next($request, $response);
}

However there's still one problem: you're hardcoding URL where you want non-authenticated users to redirected to. Consider getting that URL via it's name (that you may give when declaring route):

<?php

// Add unique name to the route so you can 
// refer to it later in the code (this is what we're going to do now)
$app->get('/user/login', YourController::class)->setName('userLoginPage');

Now you can refer to that route via name, not the URL itself in the middleware. For that you need to inject Slim Router object:

<?php

namespace Rib\Src\MiddleWares;


use Rib\Src\Services\FlashMessages;
use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Router;

class RequireLogin
{
    /**
     * Object constructor
     * 
     * Inject Slim Router here, so you can access route names.
     * 
     * @param Router $router 
     */
    public function __construct(Router $router)
    {
        $this->router = $router;
    }

    /**
     * Example middleware invokable class
     *
     * @param  \Psr\Http\Message\ServerRequestInterface $request PSR7 request
     * @param  \Psr\Http\Message\ResponseInterface $response PSR7 response
     * @param  callable $next Next middleware
     *
     * @return \Psr\Http\Message\ResponseInterface
     */
    public function __invoke( $request, $response, $next )
    {

        if ( ! isset( $_SESSION[ 'id' ] ) ) {
            FlashMessages::flashIt( 'message', "The page you tried to access requires an active session. Please log back in." );
            // Get url by name: this is more flexible than hardcoding URL
            $url = $this->router->pathFor('userLoginPage') . ' #forbidden';
            return $response->withRedirect($url);
        } else {
            return $next( $request, $response );
        }
    }

}

The benefit here is that when you change actual URL for the user login page, you don't have to change it in the middleware, since it's accessed via $router->pathFor() method.

I'd say this is very Slim, nice way. )

Upvotes: 3

Related Questions