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?
Advertisement
Answer
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 LaminasHttpPhpEnvironmentResponse(); $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.