Skip to content
Advertisement

laminas authentication – redirect to login page when using a listeneraggregate

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.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement