I am migrating an app from ZF2/3 to Laminas. I have moved the authentication from the Module.php bootstrap, to an aggregatelistener, and would like to redirect to a login page if the user is not logged in for certain routes. This worked fine originally, but i am having trouble after the migration - it is now redirecting 20 to 30 times and causing a "page is not redirecting properly" error.
My Module.php onBoostrap method:
class Module implements ConfigProviderInterface
{
/**
* @param $event
*/
public function onBootstrap(MvcEvent $event)
{
/** @var ApplicationInterface $application*/
$application = $event->getApplication();
/** @var TemplateMapResolver $templateMapResolver */
$templateMapResolver = $application->getServiceManager()->get(
'ViewTemplateMapResolver'
);
// Create and register layout listener
$listener = new LayoutListener($templateMapResolver);
$listener->attach($application->getEventManager());
//Create and register Authentication listener
$authenticationListener = new AuthenticationListener();
$authenticationListener->attach($application->getEventManager());
}
}
The AuthenticationListener class:
class AuthenticationListener extends AbstractListenerAggregate
{
/**
* @param EventManagerInterface $events
* @param int $priority
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$this->listeners[] = $events->attach(
MvcEvent::EVENT_DISPATCH,
[$this, 'userHasAuthentication']
);
}
/**
* @param MvcEvent $event
*/
public function userHasAuthentication(MvcEvent $event)
{
$authenticationService = $event->getApplication()->getServiceManager()->get(AuthenticationService::class);
if($authenticationService->hasIdentity() === false){
$event->stopPropagation(true);
// ? how to redirect
}
return true;
}
}
I have tried the following approaches to redirecting, and these still end up with the "not redirecting properly" result. Inside AuthenticationListener::userHasAuthentication:
if($authenticationService->hasIdentity() === false){
$event->stopPropagation(true);
/**@var AbstractActionController $target*/
$target = $event->getTarget();
return $target->redirect()->toRoute('auth.login');
}
...or...
if($authenticationService->hasIdentity() === false){
$event->stopPropagation(true);
/**@var AbstractActionController $target*/
$target = $event->getTarget();
$response = $target->getResponse();
$response->getHeaders()->addHeaderLine('Location', '/login');
$response->setStatusCode(403);
$response->sendHeaders();
return $response;
}
What is the correct way of achieving this?
I think you get redirection loop, cause this listener is also triggered on /login page. You have to check the current route before redirecting.
public function userHasAuthentication(MvcEvent $e)
{
$routeMatch = $e->getRouteMatch();
if ($routeMatch) {
$routeName = $routeMatch->getMatchedRouteName();
if ($routeName !== 'login' && $routeMatch->getParam('loginRequired',true)) {
$auth = $e->getApplication()->getServiceManager()->get(AuthenticationServiceInterface::class);
if ($auth->hasIdentity() === false) {
$response = new \Laminas\Http\PhpEnvironment\Response();
$response->getHeaders()->addHeaderLine('Location', "/login");
$response->setStatusCode(302);
return $response;
}
}
}
}
Adding condition on route param 'loginRequired' allows you disable redirection for chosen paths adding 'loginRequired'=>false
in 'defaults' section in route config.
BTW, if you use higher listener priority, or attach it do MvcEvent::ROUTE
, you can display login page on every path by changing route match
public function userHasAuthentication(MvcEvent $e)
{
$routeMatch = $e->getRouteMatch();
if ($routeMatch) {
$routeName = $routeMatch->getMatchedRouteName();
if ($routeName !== 'login'
&& $routeName !== 'logout'
&& $routeMatch->getParam('loginRequired', true) !== false
) {
$auth = $e->getApplication()->getServiceManager()->get(AuthenticationServiceInterface::class);
if ($auth->hasIdentity() === false) {
$routeMatch->setParam('controller', LoginController::class);
$routeMatch->setParam('action', 'login');
if ($routeName !== 'home') {
$e->getResponse()->setStatusCode(401);
}
}
}
}
}
On loginAction add
if ($this->auth->hasIdentity()) {
$this->checkTourDismiss($this->auth->getIdentity());
if (isset($_SERVER['REQUEST_URI'])
&& !in_array($_SERVER['REQUEST_URI'], ['/', '/login'])
) {
$this->redirect()->toUrl($_SERVER['REQUEST_URI']);
} else {
$this->redirect()->toRoute('home');
}
}
in the end, so after logging in user stays on URL he started with.