Auth Middleware in Slim 4

Hi All,

Request your help, the below settings errors out “Call to a member function getName() on null” , so tried to reorder the sequence of the middleware as below, still no luck

Tried - Not working

$app->add($LoginauthMiddleware);
$app->addRoutingMiddleware();

Tried - Not working

$app->addRoutingMiddleware();
$app->add($LoginauthMiddleware);

Code

use DI\Container;
use Slim\Factory\AppFactory;
use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;
use Slim\Exception\NotFoundException;
use Slim\Exception\HttpNotFoundException;
use Slim\Routing\RouteCollectorProxy;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Selective\BasePath\BasePathDetector;
use Selective\BasePath\BasePathMiddleware;
require '../Slim4/vendor/autoload.php';

$container = new Container();
AppFactory::setContainer($container);
$container->set('view', function() { return Twig::create('../tpl', ['cache' => false]); });
$app = AppFactory::create();

$LoginauthMiddleware = function($request, $handler): ResponseInterface {
    $route = $request->getAttribute('route');
    $routeName = $route->getName();
    $groups = $route->getGroups();
    $publicRoutesArray = array('login', 'allow');
    if(empty($_SESSION['user']) && (!in_array($routeName, $publicRoutesArray))) {
       return $response->withRedirect(ROOT_PATH.'login');
    } else { $response = $handler->handle($request); }
    return $response;
};
$app->add($LoginauthMiddleware);
$app->addRoutingMiddleware();
$basePath = (new BasePathDetector($_SERVER))->getBasePath();
$app->setBasePath($basePath);
$app->add(TwigMiddleware::createFromContainer($app));
$app->addErrorMiddleware(true, true, true);

Hi @Vino I see at least 2 or 3 issue in this codebase.

  1. The container is not fully initialized. Better create the PHP-DI container with the ContainerBuilder.

  2. Use RouteContext to retrieving the current route.

  3. A last tip: Use the the BasePathMiddleware as decribed here.

Hi Odan,

Thank you very much, I have followed all your step’s as below, please correct me if ou find anything incorrect and now the earlier errors got resolved, and now a new error is occurring.

Error : Call to a member function withRedirect() on null
Code

use DI\Container;
use Slim\Factory\AppFactory;
use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;
use Slim\Exception\NotFoundException;
use Slim\Exception\HttpNotFoundException;
use Slim\Routing\RouteCollectorProxy;
use Slim\Routing\RouteContext;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Selective\BasePath\BasePathDetector;
use Selective\BasePath\BasePathMiddleware;
require '../Slim4/vendor/autoload.php';

$builder = new DI\ContainerBuilder();
$container = $builder->build();
AppFactory::setContainer($container);
$container->set('view', function() { return Twig::create('../tpl', ['cache' => false]); });
$app = AppFactory::create();

$LoginauthMiddleware = function($request, $handler): ResponseInterface {
    $routeContext = RouteContext::fromRequest($request);
    $route = $routeContext->getRoute();
    if(empty($route)) { throw new NotFoundException($request, $response); }
    $routeName = $route->getName();
    $groups = $route->getGroups();
    $methods = $route->getMethods();
    $arguments = $route->getArguments();
    $publicRoutesArray = array('login', 'allow');
    if(empty($_SESSION['user']) && (!in_array($routeName, $publicRoutesArray))) {
       header("HTTP/1.1 302 Found");
       return $response->withRedirect('login');
    } else { $response = $handler->handle($request); }
    return $response;
};
$app->add($LoginauthMiddleware);
$app->addRoutingMiddleware();
$app->add(new BasePathMiddleware($app));
$app->add(TwigMiddleware::createFromContainer($app));
$app->addErrorMiddleware(true, true, true);

$app->get('/login', function($request, $response) use($app) {
      return $this->get('view')->render($response, 'page_login.html');
})->setName('login');

Ok, now it’s better :slight_smile:

You may check the Slim 4 documentation:

use Slim\Routing\RouteContext;

// Create a redirect for a named route
$routeParser = RouteContext::fromRequest($request)->getRouteParser();
$url = $routeParser->urlFor('login');

return $response->withHeader('Location', $url)->withStatus(302);

Hi Odan,

Still no luck, and error out with “Call to a member function withHeader() on null”.

Code

use DI\Container;
use Slim\Factory\AppFactory;
use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;
use Slim\Exception\NotFoundException;
use Slim\Routing\RouteContext;
use Slim\Routing\RouteCollectorProxy;
use Slim\Routing\RouteContext;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Selective\BasePath\BasePathDetector;
use Selective\BasePath\BasePathMiddleware;
require '../Slim4/vendor/autoload.php';

$builder = new DI\ContainerBuilder();
$container = $builder->build();
AppFactory::setContainer($container);
$container->set('view', function() { return Twig::create('../tpl', ['cache' => false]); });
$app = AppFactory::create();

$LoginauthMiddleware = function($request, $handler): ResponseInterface {
    $routeContext = RouteContext::fromRequest($request);
    $route = $routeContext->getRoute();
    if(empty($route)) { throw new NotFoundException($request, $response); }
    $routeName = $route->getName();
    $groups = $route->getGroups();
    $methods = $route->getMethods();
    $arguments = $route->getArguments();
    $publicRoutesArray = array('login');
    if(empty($_SESSION['user']) && (!in_array($routeName, $publicRoutesArray))) {
       $routeParser = RouteContext::fromRequest($request)->getRouteParser();
	   $url = $routeParser->urlFor('login');
       return $response->withHeader('Location', $url)->withStatus(302);
    } else { $response = $handler->handle($request); }
    return $response;
};
$app->add($LoginauthMiddleware);
$app->addRoutingMiddleware();
$app->add(new BasePathMiddleware($app));
$app->add(TwigMiddleware::createFromContainer($app));
$app->addErrorMiddleware(true, true, true);

$app->get('/login', function($request, $response) use($app) {
      return $this->get('view')->render($response, 'page_login.html');
})->setName('login');

$app->get('/dashboard', function($request, $response) use($app) {
      return $this->get('view')->render($response, 'page_dashboard.html');
})->setName('dashboard');

In a middleware you don’t get the $response from the method as parameter.

You have 3 options the retrieve or create the response:

Option 1 (default): Invoke the handle method: $response = $handler->handle($request);
Option 2: Create it manually: $response = new \Slim\Psr7\Response();
Option 3: Use the ResponseFactory.

$responseFactory = new \Slim\Psr7\Factory\ResponseFactory();
$response = $responseFactory->createResponse(200);

The correct method / usage depends on the specific functionality and context of the middleware.

Please read the documentation: https://www.slimframework.com/docs/v4/concepts/middleware.html

Hi Odan,

We Tried the option 1 and 3 , below is the code and the result(Output), request your help on the same.

Config Files

use DI\Container;
use Slim\Factory\AppFactory;
use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;
use Slim\Exception\NotFoundException;
use Slim\Routing\RouteCollectorProxy;
use Slim\Routing\RouteContext;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Nyholm\Psr7\Factory\Psr17Factory;
use Selective\BasePath\BasePathDetector;
use Selective\BasePath\BasePathMiddleware;

Option 1: Closure middleware example
Output : Does not authendicate,when visiting the url “htttps://abc.com/dashboard” rather it directly display the page

$beforeMiddleware = function($request, $handler) {
    $routeContext = RouteContext::fromRequest($request);
    $route = $routeContext->getRoute();
    if(empty($route)) { throw new NotFoundException($request, $response); }
    $routeName = $route->getName();
    $publicRoutesArray = array('login','dashboard');
    if(empty($_SESSION['user']) && (in_array($routeName, $publicRoutesArray))) {
       $routeParser = RouteContext::fromRequest($request)->getRouteParser();
       $url = $routeParser->urlFor('login');
       $response = $handler->handle($request);
       return $response->withHeader('Location', $url)->withStatus(302);
    } else { $response = $handler->handle($request); }
    return $response;
};
$app->add($beforeMiddleware);

Option 1: Invokable class middleware example
Output : Does not authendicate,when visiting the url “htttps://abc.com/dashboard” rather it directly display the page

class beforeMiddleware {

 public function __invoke($request, $handler): Response {
    $routeContext = RouteContext::fromRequest($request);
    $route = $routeContext->getRoute();
    if(empty($route)) { throw new NotFoundException($request, $response); }
    $routeName = $route->getName();
    $publicRoutesArray = array('login','dashboard');
    if(empty($_SESSION['user']) && (in_array($routeName, $publicRoutesArray))) {
       $routeParser = RouteContext::fromRequest($request)->getRouteParser();
       $url = $routeParser->urlFor('login');
       $response = $handler->handle($request);
       return $response->withHeader('Location', $url)->withStatus(302);
    } else { $response = $handler->handle($request); }
    return $response;
 }
}
$app->add(new beforeMiddleware());

Option 3: Closure middleware example - Response Factory
Output : Empty Page, No errors in the log

$beforeMiddleware =  function ($request, $handler) {
    $routeContext = RouteContext::fromRequest($request);
    $route = $routeContext->getRoute();
    if(empty($route)) { throw new NotFoundException($request, $response); }
    $routeName = $route->getName();
    $groups = $route->getGroups();
    $methods = $route->getMethods();
    $arguments = $route->getArguments();
    $publicRoutesArray = array('login','dashboard');
    if(empty($_SESSION['user']) && (in_array($routeName, $publicRoutesArray))) {
       $routeParser = RouteContext::fromRequest($request)->getRouteParser();
       $url = $routeParser->urlFor('login');
       $response = $handler->handle($request);
       $responseFactory = new \Nyholm\Psr7\Factory\Psr17Factory();
       $response = $responseFactory->createResponse(200);
       return $response->withHeader('Location', $url)->withStatus(302);
    } else { $response = $handler->handle($request); }
    return $response;
};
$app->add($beforeMiddleware);

Option 3 :Invokable class middleware example - Response Factory
Output : Empty Page, No errors in the log

class beforeMiddleware {

 public function __invoke($request, $handler): Response {
    $routeContext = RouteContext::fromRequest($request);
    $route = $routeContext->getRoute();
    if(empty($route)) { throw new NotFoundException($request, $response); }
    $routeName = $route->getName();
    $publicRoutesArray = array('login','dashboard');
    if(empty($_SESSION['user']) && (in_array($routeName, $publicRoutesArray))) {
       $routeParser = RouteContext::fromRequest($request)->getRouteParser();
       $url = $routeParser->urlFor('login');
       $response = $handler->handle($request);
       $responseFactory = new \Nyholm\Psr7\Factory\Psr17Factory();
       $response = $responseFactory->createResponse(200);
       return $response->withHeader('Location', $url)->withStatus(302);
    } else { $response = $handler->handle($request); }
    return $response;
 }
}
$app->add(new beforeMiddleware());

Hi Odan,

We are getting the below error with the below code, we have removed the base path middle ware , because in one of your doc provided in this url “https://odan.github.io/2019/11/05/slim4-tutorial.html#routing-setup” is states that we have to set base path nor use base path middle ware only if we are running the project within a sub directory under the folder /public where as in our setup we have our project above the folder /public and the folder /public contains only index.php and we have configured our project structure as same as defined in the url doc and we get this error only wen we visit the route(/require) which we require to be authenticated, rest of the routes are working perfectly without any error.

Error

Got error 'PHP message: 404 Not Found\n
Type: Slim\\Exception\\HttpNotFoundException\n
Code: 404\n
Message: Not found.\n
File: /Project/test/web/Slim4/vendor/slim/slim/Slim/Middleware/RoutingMiddleware.php\n
Line: 93\n
Trace: #0 /Project/test/web/Slim4/vendor/slim/slim/Slim/Middleware/RoutingMiddleware.php(59): 
Slim\\Middleware\\RoutingMiddleware->performRouting(Object(Nyholm\\Psr7\\ServerRequest))\n
#1 /Project/test/web/Slim4/vendor/slim/slim/Slim/MiddlewareDispatcher.php(140): Slim\\Middleware\\RoutingMiddleware->process(Object(Nyholm\\Psr7\\ServerRequest), Object(class@anonymous))\n
#2 /Project/test/web/Slim4/vendor/slim/twig-view/src/TwigMiddleware.php(125): class@anonymous->handle(Object(Nyholm\\Psr7\\ServerRequest))\n
#3 /Project/test/web/Slim4/vendor/slim/slim/Slim/MiddlewareDispatcher.php(140): Slim\\Views\\TwigMiddleware->process(Object(Nyholm\\Psr7\\ServerRequest), Object(class@anonymous))\n
#4 /Project/test/web/Slim4/vendor/slim...\n'

Code

class authorization {
      public function __invoke($request, $handler): Response {
                      $routeContext = RouteContext::fromRequest($request);
                      $route = $routeContext->getRoute();
                      if(empty($route)) { throw new NotFoundException($request, $response); }
                      $routeName = $route->getName();
                      $publicRoutesArray = array('require');
                      if(empty($_SESSION['user']) && (in_array($routeName, $publicRoutesArray))) {
                         $routeParser = RouteContext::fromRequest($request)->getRouteParser();
                         $url = $routeParser->urlFor('login');
                         $response = $handler->handle($request);
                         $responseFactory = new \Nyholm\Psr7\Factory\Psr17Factory();
                         $response = $responseFactory->createResponse(200);
                         return $response->withHeader('Location', $url)->withStatus(302);
                      } else { $response = $handler->handle($request); }
             return $response;
      }
}

No, this is not the correct interpretation.

For security reasons you should always place your front-controller (index.php) into the public/ directory.

With sub-directory I don’t mean the public/ directory. In this context “sub-directory” means a subdirectory of the project. For example when you place your app not directly under the webservers RootDirectory.

Second. Why do you overwrite the response object here??? This makes no sense.
Choose one of the options, but not two at the same time.

$response = $handler->handle($request);
$responseFactory = new \Nyholm\Psr7\Factory\Psr17Factory();
$response = $responseFactory->createResponse(200);

If you read the documentation and my answers carefully you might find a solution. That’s all I can help you with.

Hi Odan,

Thank you very much, we tried the below

  • Removing the line “$response = $handler->handle($request);” and tried with other 2 lines no luck - Blank page - Slim\Exception\HttpNotFoundException \n Code: 404
  • Vice versa too, still no luck - Blank page - Slim\Exception\HttpNotFoundException \n Code: 404
  • Added the base path middle ware for both the test.

At last option can you please check whether the placement of “.htaccess” file is correct, based on the below setup.

/var/www/test
  .htaccess
  Smil4
     	composer.json  composer.lock  source  vendor
  config
  public
    	.htaccess index.php
  template
  temp

middleware.php

<?php

use Selective\Config\Configuration;
use Slim\App;
use Slim\Middleware\ErrorMiddleware;
use Slim\Views\TwigMiddleware;
use Slim\Middleware\RoutingMiddleware;
use Selective\BasePath\BasePathMiddleware;

require_once('bpauthurization.php');

return function (App $app) {
       $routingMiddleware = new Slim\Middleware\RoutingMiddleware(
                $app->getRouteResolver(),
                $app->getRouteCollector()->getRouteParser()
       );
       $app->add(new authorization());
       $app->addBodyParsingMiddleware();
       $app->addMiddleware($routingMiddleware);
       $app->add(new BasePathMiddleware($app));
       $app->add(TwigMiddleware::createFromContainer($app));
       $app->add(ErrorMiddleware::class);
};
?>

authorization.php

<?php
use Slim\Routing\RouteContext;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Exception\NotFoundException;
use Nyholm\Psr7\Factory\Psr17Factory;
class authorization {
      public function __invoke($request, $handler): Response {
                      $routeContext = RouteContext::fromRequest($request);
                      $route = $routeContext->getRoute();
                      if(empty($route)) { throw new NotFoundException($request, $response); }
                      $routeName = $route->getName();
                      $publicRoutesArray = array('f401');
                      if(empty($_SESSION['user']) && (in_array($routeName, $publicRoutesArray))) {
                         $routeParser = RouteContext::fromRequest($request)->getRouteParser();
                         $url = $routeParser->urlFor('login');
                         $responseFactory = new \Nyholm\Psr7\Factory\Psr17Factory();
                         $response = $responseFactory->createResponse(200);
                         return $response->withHeader('Location', $url)->withStatus(302);
                      } else { $response = $handler->handle($request); }
             return $response;
      }
}