How to authenticate for the whole module level application?

zend-mvc

#1

Hi, I am working on how to anthenticate and authorize for my member module in ZF3 project

I worked with zf2 for a while, and remember that in ZF2, we used to do with the following:

in the module.php for the module. we wrote an onBootstrap function like:

    public function onBootstrap(MvcEvent $e)
    {
        $eventManager = $e->getApplication()->getEventManager();
        $eventManager->attach('dispatch', array($this, 'setLayout'),2);
        $eventManager->attach('route', array($this, 'doAuthorization'),3);
        $moduleRouteListener = new ModuleRouteListener();
        $moduleRouteListener->attach($eventManager);
    }

    public function doAuthorization(MvcEvent $e)
    {
        $application = $e->getApplication();
        $sm = $application->getServiceManager();
        $sharedManager = $application->getEventManager()->getSharedManager();
        $router = $sm->get('router');
        $request = $sm->get('request');
        $matchedRoute = $router->match($request);
        if (!preg_match('%^/'.__NAMESPACE__.'/.*%i', $request->getUri()->getPath())) return true;
        if (null !== $matchedRoute) {
            $sharedManager->attach('Zend\Mvc\Controller\AbstractActionController','dispatch',
                function($e) use ($sm) {
                    $sm->get('ControllerPluginManager')->get('Appauth')->doAuthorization($e);
                },4
            );
        }
    }

then we wrote the main authorize logic in the controller plugin like:

<?php
namespace Members\Controller\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
class Appauth extends AbstractPlugin
{	
	public function doAuthorization($e)  //just consider the module members
	{
		$matches = $e->getRouteMatch();
		$controller = $matches->getParam('controller');
		$action = $matches->getParam('action');
		
		$membersTable = $this->getController()->getServiceLocator()->get('Members\Model\MembersTable');
		$userInfo = $membersTable->userInfo();
		$rawUri = strtolower($e->getRequest()->getUri()->getPath()); // /members/index/registration /members/index /members
		$uri = substr($rawUri, 0, strpos($rawUri, $action)).$action;
		$response = $e->getResponse();
		$router = $e->getRouter();
		if ($controller == 'Members\Controller\Index' && $action == 'login') //if it is login page
		{
			if ($userInfo)
			{
				$url = $router->assemble(array('controller'=>'index','action'=>'index'),array('name'=>'members'));
				$response->getHeaders()->addHeaderLine('Location', $url);
				$response->setStatusCode(302);
				$e->stopPropagation(true);
				return $response;
			}
			else 
			{
				return true;
			}
		}
		if ($controller == 'Members\Controller\Index' && $action == 'logout')
		{
			return true;
		}
		if ($controller == 'Members\Controller\Index' && $action == 'register')
		{
			if ($userInfo)
			{
				$url = $router->assemble(array('controller'=>'index','action'=>'index'),array('name'=>'members'));
				$response->getHeaders()->addHeaderLine('Location', $url);
				$response->setStatusCode(302);
				$e->stopPropagation(true);
				return $response;
			}
			else 
			{
				return true;
			}
		}
		if (!$userInfo) //others not logined will redirect to login page
		{
			$url = $router->assemble(array('controller'=>'index','action'=>'login'),array('name'=>'members'));
			$response->getHeaders()->addHeaderLine('Location', $url);
			$response->setStatusCode(302);
			$e->stopPropagation(true);
			return $response;
		}
		else 
		{
			if ($controller == 'Members\Controller\Index' && $action == 'index') return true;
			if ($userInfo['rid'] == 1) return true;
			$resourcesArr = $this->getController()->getServiceLocator()->get('Members\Model\ResourcesTable')->formatResources('iu');
			$sid = array_search($uri,$resourcesArr);
			if (!$sid) //which is not included in resourcelist
			{
				$url = $router->assemble(array('controller'=>'index','action'=>'index'),array('name'=>'members'));
				$response->getHeaders()->addHeaderLine('Location', $url);
				$response->setStatusCode(302);
				$e->stopPropagation(true);
				return $response;
			}
			else 
			{
				if ($this->getController()->getServiceLocator()->get('Members\Model\AclTable')->checkAcl($userInfo['rid'],$sid))
				{
					return true;
				}
				else 
				{
					$url = $router->assemble(array('controller'=>'index','action'=>'index'),array('name'=>'members'));
					$response->getHeaders()->addHeaderLine('Location', $url);
					$response->setStatusCode(302);
					$e->stopPropagation(true);
					return $response;
				}
			}
		}
	}
}

and config the plugin in the moudule.config.php like:

    'controller_plugins' => array(
        'invokables' => array(
            'Appauth' => 'Members\Controller\Plugin\Appauth',
        ),
    ),

I wonder how it looks like in ZF3, when I wrote this new topic, it says my topic is similar to https://discourse.zendframework.com/t/the-right-mvc-event-to-tie-into-for-authorization/614, I am not well fit this way. I think it looks like yii framework, besides, It is a little complicate to me. I am seeking a way which is similar to the code I pasted above. Is there any ZF3-patterned one like the code above?

I prefer to write the main logic in one file with acl.

Thank you for your time.


#2

This article on zf-mvc-auth may be helpful: https://blog.tomhanderson.com/2017/10/using-zf-mvc-auth-for-custom.html


#3

Hi TomHAnderson, Thank you for taking your time.

I am not familar to Doctrine, in fact I have never used Doctrine. if I just use the tablegateway like the zf3 office demo, what will the code look like? I can figure out you are very good at configuration. I am at very low level in configuration the zf3.


#4

Hi jobsfan

the zf3 logic is quite the same

on Module.php::onBootstrap, I attach an event listener (note: on Dispatch event. the dispatch event occurs after bootstrap and after route events so the route will be available)

    $eventManager = $mvcEvent->getApplication()->getEventManager();
    $sharedEvents = $eventManager->getSharedManager();

    // we only check login status for the current namespace (note the high priority) :
    $sharedEvents->attach('Zend\Mvc\Controller\AbstractActionController',
        MvcEvent::EVENT_DISPATCH, array($this, 'checkLoggedStatus'), 1000);

the checkLoggedStatus will then make use of the authentication service the :

    $authService = $sm->get('Zend\Authentication\AuthenticationService');
    if (!$authService->hasIdentity()) {...  

Hope this helps


#5

Have a good look at https://olegkrivtsov.github.io/using-zend-framework-3-book/html/en/User_Management__Authentication_and_Access_Filtering.html There’s a lot of good information there which hopefully you will find helpful.


#6

Hi jcaillot,
Thanks for your reply!

I do not just want to check the user logined or not, also I need use the acl component to authorized via url(by module, controller,action), you know that. I do not want to write the acl logic in the module.php, I thought there might by a suit place the logic, I do not know whether controller plugin is the best one, but I just found this demo from the internet. If there are any other place to set the acl logic, pls let me known. I can not really clear figure out the differences between model, service, or controller plugin, etc. I guess it might be a habit when structure the code, or there are some difference on the progress sequence, some execute earlier and some later, if you know, pls tell me.

Thank you again.


#7

hello davidmintz,
Thanks for your reply.

I mentioned the olegkrivtsov.github.io in my topic. unfortunately, I am not familiar to doctrine stuff, in fact, I have never used doctrine. of course I will learn later when I have time, but for now, I need to push the project to go on. Yesterday I cloned the sample code on the github of the website you recommend.
QQ%E6%88%AA%E5%9B%BE20181211093658
Sorry that, I do not well understand it, and as well, I did not make it run on my computer. I think my php level is not enough for me to use this manner.

Anyway, thanks again for your reply.


#8

ugh, my apologies for not noticing what you said about Doctrine.


#9

@ jobsfan

the best place for an ACL logic would e IMHO in a service.
A service is basically a class that the service container (aka service manager) instantiates (an injects as method param) when needed. In ZF3 pretty everything is a service. When one uses the word it more precisely means a custom class (and is factory) declared under the service manager key in config :

‘service_manager’ => [
‘factories’ => [

         'Application\My\ACL\Provider'    => 'Application\My\ACL\ ProviderFactory',

#10

@jcaillot thanks for your advice, I also think place the acl logic in service is much better than plugin. I will try. Thank you again.