Created
August 25, 2016 07:30
-
-
Save rogatec/6ba7bcd418012193747cdaf7d870209c to your computer and use it in GitHub Desktop.
Zend Framework 3 Authentication with Session setup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Authentication\Controller; | |
use Employee\Model\EmployeeTable; | |
use Main\Form\LoginForm; | |
use Zend\Authentication\Adapter\DbTable\CredentialTreatmentAdapter; | |
use Zend\Authentication\AuthenticationService; | |
use Zend\Db\Sql\Select; | |
use Zend\Mvc\Controller\AbstractActionController; | |
use Zend\Mvc\Plugin\FlashMessenger\FlashMessenger; | |
class AuthController extends AbstractActionController | |
{ | |
/** | |
* @var AuthenticationService | |
*/ | |
private $authService; | |
/** | |
* @var EmployeeTable | |
*/ | |
private $employeeTable; | |
/** | |
* AuthController constructor. | |
* | |
* @param AuthenticationService $authService | |
* @param EmployeeTable $employeeTable | |
*/ | |
public function __construct(AuthenticationService $authService, EmployeeTable $employeeTable) | |
{ | |
$this->authService = $authService; | |
$this->employeeTable = $employeeTable; | |
} | |
/** | |
* @return ViewModel | |
*/ | |
public function loginAction() | |
{ | |
$form = new LoginForm(); | |
$request = $this->getRequest(); | |
if (!$request->isPost()) { | |
return ['form' => $form]; | |
} | |
$form->setData($request->getPost()); | |
if (!$form->isValid()) { | |
$this->flashMessenger()->addMessage("your error message", FlashMessenger::NAMESPACE_ERROR); | |
return ['form' => $form]; | |
} | |
/** @var Employee $employeeObject */ | |
$employeeObject = $this->employeeTable->getEmployeeByLogin($request->getPost('system_name'), $request->getPost('password')); | |
if (!$employeeObject) { | |
$this->flashMessenger()->addMessage("your error message", FlashMessenger::NAMESPACE_ERROR); | |
return ['form' => $form]; | |
} | |
if ($this->authenticate($employeeObject)) { | |
return $this->redirect()->toRoute('main'); | |
} | |
$this->flashMessenger()->addMessage("your error message", FlashMessenger::NAMESPACE_ERROR); | |
return ['form' => $form]; | |
} | |
public function logoutAction() | |
{ | |
$this->authService->clearIdentity(); | |
return $this->redirect()->toRoute('login'); | |
} | |
/** | |
* @param $employeeObject | |
* | |
* @return array | |
*/ | |
private function authenticate($employeeObject) | |
{ | |
/** @var CredentialTreatmentAdapter $adapter */ | |
$adapter = $this->authService->getAdapter(); | |
$select = $adapter->getDbSelect(); | |
$select->join(['R' => 'role'], 'role_id = R.id', ['role' => 'description'], Select::JOIN_LEFT); | |
$this->authService->setAdapter($adapter); | |
$this->authService->getAdapter()->setIdentity($employeeObject->system_name)->setCredential($employeeObject->password); | |
$result = $this->authService->authenticate(); | |
if ($result->isValid()) { | |
$resultRow = $this->authService->getAdapter()->getResultRowObject(); | |
$this->authService->getStorage()->write([ | |
'id' => $resultRow->id, | |
'system_name' => $resultRow->system_name, | |
'role' => $resultRow->role, | |
]); | |
return true; | |
} | |
return false; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Authentication\Factory; | |
use Employee\Model\EmployeeTable; | |
use Interop\Container\ContainerInterface; | |
use Interop\Container\Exception\ContainerException; | |
use Authentication\Controller\AuthController; | |
use Zend\Authentication\AuthenticationService; | |
use Zend\ServiceManager\Exception\ServiceNotCreatedException; | |
use Zend\ServiceManager\Exception\ServiceNotFoundException; | |
use Zend\ServiceManager\Factory\FactoryInterface; | |
class AuthControllerFactory implements FactoryInterface | |
{ | |
/** | |
* Create an object | |
* | |
* @param ContainerInterface $container | |
* @param string $requestedName | |
* @param null|array $options | |
* | |
* @return object | |
* @throws ServiceNotFoundException if unable to resolve the service. | |
* @throws ServiceNotCreatedException if an exception is raised when | |
* creating a service. | |
* @throws ContainerException if any other error occurs | |
*/ | |
public function __invoke(ContainerInterface $container, $requestedName, array $options = null) | |
{ | |
return new AuthController($container->get(AuthenticationService::class), $container->get(EmployeeTable::class)); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Authentication\Factory; | |
use Authentication\Storage\AuthStorage; | |
use Interop\Container\ContainerInterface; | |
use Interop\Container\Exception\ContainerException; | |
use Zend\Authentication\Adapter\DbTable\CredentialTreatmentAdapter; | |
use Zend\Authentication\AuthenticationService; | |
use Zend\Db\Adapter\AdapterInterface; | |
use Zend\ServiceManager\Exception\ServiceNotCreatedException; | |
use Zend\ServiceManager\Exception\ServiceNotFoundException; | |
use Zend\ServiceManager\Factory\FactoryInterface; | |
class AuthenticationServiceFactory implements FactoryInterface | |
{ | |
/** | |
* Create an object | |
* | |
* @param ContainerInterface $container | |
* @param string $requestedName | |
* @param null|array $options | |
* | |
* @return object | |
* @throws ServiceNotFoundException if unable to resolve the service. | |
* @throws ServiceNotCreatedException if an exception is raised when | |
* creating a service. | |
* @throws ContainerException if any other error occurs | |
*/ | |
public function __invoke(ContainerInterface $container, $requestedName, array $options = null) | |
{ | |
$dbAdapter = $container->get(AdapterInterface::class); | |
$authAdapter = new CredentialTreatmentAdapter($dbAdapter, 'employee', 'system_name', 'password'); | |
return new AuthenticationService($container->get(AuthStorage::class), $authAdapter); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Authentication\Storage; | |
use Zend\Authentication\Storage\Session; | |
use Zend\Authentication\Storage\StorageInterface; | |
class AuthStorage implements StorageInterface | |
{ | |
const NAME = 'your_session_name'; | |
/** | |
* @var StorageInterface | |
*/ | |
private $storage; | |
/** | |
* @var mixed | |
*/ | |
private $resolvedIdentity; | |
/** | |
* Returns true if and only if storage is empty | |
* | |
* @throws \Zend\Authentication\Exception\ExceptionInterface If it is impossible to determine whether storage is empty | |
* @return bool | |
*/ | |
public function isEmpty() | |
{ | |
if ($this->getStorage()->isEmpty()) { | |
return true; | |
} | |
$identity = $this->getStorage()->read(); | |
if ($identity === null) { | |
$this->clear(); | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Returns the contents of storage | |
* | |
* Behavior is undefined when storage is empty. | |
* | |
* @throws \Zend\Authentication\Exception\ExceptionInterface If reading contents from storage is impossible | |
* @return mixed | |
*/ | |
public function read() | |
{ | |
if ($this->resolvedIdentity !== null) { | |
return $this->resolvedIdentity; | |
} | |
$identity = $this->getStorage()->read(); | |
if ($identity) { | |
$this->resolvedIdentity = $identity; | |
} else { | |
$this->resolvedIdentity = null; | |
} | |
return $this->resolvedIdentity; | |
} | |
/** | |
* Writes $contents to storage | |
* | |
* @param mixed $contents | |
* | |
* @throws \Zend\Authentication\Exception\ExceptionInterface If writing $contents to storage is impossible | |
* @return void | |
*/ | |
public function write($contents) | |
{ | |
$this->resolvedIdentity = null; | |
$this->getStorage()->write($contents); | |
} | |
/** | |
* Clears contents from storage | |
* | |
* @throws \Zend\Authentication\Exception\ExceptionInterface If clearing contents from storage is impossible | |
* @return void | |
*/ | |
public function clear() | |
{ | |
$this->resolvedIdentity = null; | |
$this->getStorage()->clear(); | |
} | |
/** | |
* @param StorageInterface $storage | |
* | |
* @return AuthStorage $this | |
*/ | |
public function setStorage(StorageInterface $storage) | |
{ | |
$this->storage = $storage; | |
return $this; | |
} | |
/** | |
* @return StorageInterface | |
*/ | |
public function getStorage() | |
{ | |
if ($this->storage === null) { | |
$this->setStorage(new Session(self::NAME)); | |
} | |
return $this->storage; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use Authentication\Controller\AuthController; | |
use Zend\Router\Http\Literal; | |
return [ | |
'router' => [ | |
'routes' => [ | |
'login' => [ | |
'type' => Literal::class, | |
'options' => [ | |
'route' => '/login', | |
'defaults' => [ | |
'controller' => AuthController::class, | |
'action' => 'login', | |
], | |
], | |
], | |
'logout' => [ | |
'type' => Literal::class, | |
'options' => [ | |
'route' => '/logout', | |
'defaults' => [ | |
'controller' => AuthController::class, | |
'action' => 'logout', | |
], | |
], | |
], | |
], | |
], | |
'controllers' => [ | |
'factories' => [ | |
\Authentication\Controller\AuthController::class => \Authentication\Factory\AuthControllerFactory::class, | |
], | |
], | |
'service_manager' => [ | |
'factories' => [ | |
\Authentication\Storage\AuthStorage::class => \Zend\ServiceManager\Factory\InvokableFactory::class, | |
\Zend\Authentication\AuthenticationService::class => \Authentication\Factory\AuthenticationServiceFactory::class, | |
], | |
], | |
'view_manager' => [ | |
'template_path_stack' => [ | |
'Authentication' => __DIR__ . '/../view', | |
], | |
], | |
]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// located at $PROJECT_ROOT/config/autoload | |
use Zend\Db\Adapter\AdapterAbstractServiceFactory; | |
/** | |
* configurations across all modules - specify sensitive data and personal settings in your local.php | |
*/ | |
return [ | |
'session_validators' => [ | |
\Zend\Session\Validator\RemoteAddr::class, | |
\Zend\Session\Validator\HttpUserAgent::class, | |
], | |
'session_config' => [ | |
'remember_me_seconds' => 604800, // one week | |
'use_cookies' => true, | |
'cookie_lifetime' => 604800, // one week | |
'name' => 'your_session_name', | |
], | |
'session_storage' => [ | |
'type' => \Zend\Session\Storage\SessionArrayStorage::class, | |
], | |
]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Main; | |
use Main\Factory\SessionManagerFactory; | |
use Zend\Session\SessionManager; | |
return [ | |
'service_manager' => [ | |
'factories' => [ | |
SessionManager::class => SessionManagerFactory::class, | |
], | |
], | |
]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Main; | |
use Zend\Authentication\AuthenticationService; | |
use Zend\Http\Request; | |
use Zend\Mvc\MvcEvent; | |
use Zend\Session\Container; | |
use Zend\Session\SessionManager; | |
use Zend\Session\Validator\HttpUserAgent; | |
use Zend\Session\Validator\RemoteAddr; | |
class Module | |
{ | |
/** | |
* @var AuthenticationService | |
*/ | |
private $auth; | |
public function getConfig() | |
{ | |
return include __DIR__ . '/../config/module.config.php'; | |
} | |
public function onBootstrap(MvcEvent $mvcEvent) | |
{ | |
$this->bootstrapSession($mvcEvent); | |
$this->auth = $mvcEvent->getApplication()->getServiceManager()->get(AuthenticationService::class); | |
// store user and role in global viewmodel | |
if ($this->auth->hasIdentity()) { | |
// for e.g. store your auth identity into ViewModel | |
$mvcEvent->getViewModel()->setVariable('authIdentity', $this->auth->getIdentity()); | |
// extend functionality with acl to checkPermission if user has rights to the given route | |
// ... | |
} else { | |
// redirect if auth fails for example back to /login | |
// .. | |
} | |
} | |
/** | |
* @param MvcEvent $e | |
*/ | |
private function bootstrapSession($e) | |
{ | |
/** @var SessionManager $session */ | |
$session = $e->getApplication()->getServiceManager()->get(SessionManager::class); | |
$session->start(); | |
$container = new Container('your_session_name', $session); | |
if (isset($container->init)) { | |
return; | |
} | |
/** @var Request $request */ | |
$request = $e->getRequest(); | |
$session->regenerateId(true); | |
$container->init = 1; | |
$container->remoteAddr = $request->getServer()->get('REMOTE_ADDR'); | |
$container->httpUserAgent = $request->getServer()->get('HTTP_USER_AGENT'); | |
$config = $e->getApplication()->getServiceManager()->get('config'); | |
if (!isset($config['session'])) { | |
return; | |
} | |
if (!isset($config['session_validators'])) { | |
return; | |
} | |
$chain = $session->getValidatorChain(); | |
foreach ($config['session_validators'] as $validator) { | |
switch ($validator) { | |
case HttpUserAgent::class: | |
$validator = new $validator($container->httpUserAgent); | |
break; | |
case RemoteAddr::class: | |
$validator = new $validator($container->remoteAddr); | |
break; | |
default: | |
$validator = new $validator(); | |
} | |
$chain->attach('session.validate', [$validator, 'isValid']); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Main\Factory; | |
use Interop\Container\ContainerInterface; | |
use Interop\Container\Exception\ContainerException; | |
use Zend\ServiceManager\Exception\ServiceNotCreatedException; | |
use Zend\ServiceManager\Exception\ServiceNotFoundException; | |
use Zend\ServiceManager\Factory\FactoryInterface; | |
use Zend\Session\Config\SessionConfig; | |
use Zend\Session\Container; | |
use Zend\Session\SessionManager; | |
class SessionManagerFactory implements FactoryInterface | |
{ | |
/** | |
* Create an object | |
* | |
* @param ContainerInterface $container | |
* @param string $requestedName | |
* @param null|array $options | |
* | |
* @return object | |
* @throws ServiceNotFoundException if unable to resolve the service. | |
* @throws ServiceNotCreatedException if an exception is raised when | |
* creating a service. | |
* @throws ContainerException if any other error occurs | |
*/ | |
public function __invoke(ContainerInterface $container, $requestedName, array $options = null) | |
{ | |
$config = $container->get('config'); | |
if (!isset($config['session_config'])) { | |
$sessionManager = new SessionManager(); | |
Container::setDefaultManager($sessionManager); | |
return $sessionManager; | |
} | |
// create session config if exists in global configuration | |
$sessionConfig = null; | |
if (isset($config['session_config'])) { | |
$sessionConfig = new SessionConfig(); | |
$sessionConfig->setOptions($config['session_config']); | |
} | |
// create session storage if exists in global configuration | |
$sessionStorage = null; | |
if (isset($config['session_storage'])) { | |
$class = $config['session_storage']['type']; | |
$sessionStorage = new $class('hpv'); | |
} | |
// optional get a save handler and store it into SessionManager (currently null) | |
$sessionManager = new SessionManager( | |
$sessionConfig, | |
$sessionStorage, | |
null | |
); | |
Container::setDefaultManager($sessionManager); | |
return $sessionManager; | |
} | |
} |
thanks great work!!
I'm starting using Laminas 20 days ago, and i try implement your solution on mvc skeleton, but i get this error :Fatal error: Uncaught Laminas\ServiceManager\Exception\ServiceNotFoundException: Unable to resolve service "Laminas\Authentication\AuthenticationService" to a factory; are you certain you provided it during configuration?
on Line 29 of Module.php of Main (in my app is Application) module.
can you help me?
Hi 🙋, well to be honest I don’t develop anymore with Zend (didn’t know it’s called Laminas now).
But this error looks like, that the AuthenticationService
is missing (originally in Zend\Authentication\AuthenticationService
).
Take a look at the documentation - 🤞🏽
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
thanks great work!!
I'm starting using Laminas 20 days ago, and i try implement your solution on mvc skeleton, but i get this error :
Fatal error: Uncaught Laminas\ServiceManager\Exception\ServiceNotFoundException: Unable to resolve service "Laminas\Authentication\AuthenticationService" to a factory; are you certain you provided it during configuration?
on Line 29 of Module.php of Main (in my app is Application) module.
can you help me?