Zend Framework 2 : Working with AuthenticationService and Session Db Save Handler
One of the Session SaveHandlers that Zend Framework 2 provide is DbTableGateway Save Handler that utilize Zend\Db as a session save handler. How to combine authentication service with it ? Let’s learn about it! Save Handlers themselves are decoupled from PHP’s save handler functions and are only implemented as a PHP save handler when utilized in conjunction with Zend\Session\SessionManager.
1. Preparation
a. create tables
-- table to save session data.... CREATE TABLE IF NOT EXISTS `session` ( `id` char(32) NOT NULL DEFAULT '', `name` char(32) NOT NULL DEFAULT '', `modified` int(11) DEFAULT NULL, `lifetime` int(11) DEFAULT NULL, `data` text, PRIMARY KEY (`id`,`name`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- users table CREATE TABLE IF NOT EXISTS `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(100) NOT NULL, `password` varchar(100) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1; -- users data with password = md5('admin') INSERT INTO `users` (`id`, `username`, `password`) VALUES (1, 'admin', '21232f297a57a5a743894a0e4a801fc3');
b. create a module with structure like the following ( don’t judge me I’m not explain file location again :p )
2. Create Classes
a. AuthStorage
It’s a class that extends Zend\Authentication\Storage\Session and utilize SessionManager to set Db Handler.
//filename : module/SanAuthWithDbSaveHandler/src/SanAuthWithDbSaveHandler/Storage/AuthStorage.php namespace SanAuthWithDbSaveHandler\Storage; use Zend\ServiceManager\ServiceLocatorAwareInterface; use Zend\ServiceManager\ServiceLocatorInterface; use Zend\Authentication\Storage; use Zend\Session\Config\SessionConfig; use Zend\Db\TableGateway\TableGateway; use Zend\Session\SaveHandler\DbTableGateway; use Zend\Session\SaveHandler\DbTableGatewayOptions; class AuthStorage extends Storage\Session implements ServiceLocatorAwareInterface { protected $serviceLocator; protected $namespace; public function __construct($namespace = null) { parent::__construct($namespace); $this->namespace = $namespace; } public function setDbHandler() { $tableGateway = new TableGateway('session', $this->getServiceLocator() ->get('Zend\Db\Adapter\Adapter')); $saveHandler = new DbTableGateway($tableGateway, new DbTableGatewayOptions()); //open session $sessionConfig = new SessionConfig(); $saveHandler ->open($sessionConfig->getOption('save_path'), $this->namespace); //set save handler with configured session $this->session->getManager()->setSaveHandler($saveHandler); } public function write($contents) { parent::write($contents); /** when $this->authService->authenticate(); is valid, the session automatically called write('username') in this case, i want to save data like ["storage"] => array(4) { ["id"] => string(1) "1" ["username"] => string(5) "admin" ["ip_address"] => string(9) "127.0.0.1" ["user_agent"] => string(81) "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:18.0) Gecko/20100101 Firefox/18.0" }*/ if (is_array($contents) && !empty($contents)) { $this->getSessionManager() ->getSaveHandler() ->write($this->getSessionId(), \Zend\Json\Json::encode($contents)); } } public function clear() { $this->getSessionManager()->getSaveHandler()->destroy($this->getSessionId()); parent::clear(); } public function getSessionManager() { return $this->session->getManager(); } public function getSessionId() { return $this->session->getManager()->getId(); } public function setServiceLocator(ServiceLocatorInterface $serviceLocator) { $this->serviceLocator = $serviceLocator; } public function getServiceLocator() { return $this->serviceLocator; } }
b. AuthStorageFactory
Remember, that setting up service via closure is bad for performance, so we need to make a factory class for it.
//filename : module/SanAuthWithDbSaveHandler/src/SanAuthWithDbSaveHandler/Factory/Storage/AuthStorageFactory.php namespace SanAuthWithDbSaveHandler\Factory\Storage; use Zend\ServiceManager\FactoryInterface; use Zend\ServiceManager\ServiceLocatorInterface; use SanAuthWithDbSaveHandler\Storage\AuthStorage; class AuthStorageFactory implements FactoryInterface { public function createService(ServiceLocatorInterface $serviceLocator) { $storage = new AuthStorage('my_storage_namespace'); $storage->setServiceLocator($serviceLocator); $storage->setDbHandler(); return $storage; } }
c. AuthenticationServiceFactory
This is a Class that call AuthStorage class.
//filename : module/SanAuthWithDbSaveHandler/src/SanAuthWithDbSaveHandler/Factory/Storage/AuthenticationServiceFactory.php namespace SanAuthWithDbSaveHandler\Factory\Storage; use Zend\ServiceManager\FactoryInterface; use Zend\ServiceManager\ServiceLocatorInterface; use Zend\Authentication\AuthenticationService; use Zend\Authentication\Adapter\DbTable as DbTableAuthAdapter; class AuthenticationServiceFactory implements FactoryInterface { public function createService(ServiceLocatorInterface $serviceLocator) { $dbAdapter = $serviceLocator->get('Zend\Db\Adapter\Adapter'); $dbTableAuthAdapter = new DbTableAuthAdapter($dbAdapter, 'users', 'username','password', 'MD5(?)'); $authService = new AuthenticationService($serviceLocator->get('AuthStorage'), $dbTableAuthAdapter); return $authService; } }
d. LoginForm class
//filename : module/SanAuthWithDbSaveHandler/src/SanAuthWithDbSaveHandler/Form/LoginForm.php namespace SanAuthWithDbSaveHandler\Form; use Zend\Form\Form; use Zend\InputFilter; class LoginForm extends Form { public function __construct() { parent::__construct(); $this->setAttribute('method', 'post'); $this->add(array( 'name' => 'username', 'type' => 'Text', 'options' => array( 'label' => 'Username : ' ), )); $this->add(array( 'name' => 'password', 'type' => 'Password', 'options' => array( 'label' => 'Password : ' ), )); $this->add(array( 'name' => 'Loginsubmit', 'type' => 'Submit', 'attributes' => array( 'value' => 'Login', 'id' => 'Loginsubmit', ), )); $this->setInputFilter($this->createInputFilter()); } public function createInputFilter() { $inputFilter = new InputFilter\InputFilter(); //username $username = new InputFilter\Input('username'); $username->setRequired(true); $inputFilter->add($username); //password $password = new InputFilter\Input('password'); $password->setRequired(true); $inputFilter->add($password); return $inputFilter; } }
e. AuthController
This is a controller that place a Login Form and authentication service.
//filename : module/SanAuthWithDbSaveHandler/src/SanAuthWithDbSaveHandler/Controller/AuthController.php namespace SanAuthWithDbSaveHandler\Controller; use Zend\Mvc\Controller\AbstractActionController; use Zend\Authentication\AuthenticationService; use Zend\View\Model\ViewModel; class AuthController extends AbstractActionController { protected $authService; //we will inject authService via factory public function __construct(AuthenticationService $authService) { $this->authService = $authService; } public function indexAction() { if ($this->authService->getStorage()->getSessionManager() ->getSaveHandler() ->read($this->authService->getStorage()->getSessionId())) { //redirect to success controller... return $this->redirect()->toRoute('success'); } $form = $this->getServiceLocator() ->get('FormElementManager') ->get('SanAuthWithDbSaveHandler\Form\LoginForm'); $viewModel = new ViewModel(); //initialize error... $viewModel->setVariable('error', ''); //authentication block... $this->authenticate($form, $viewModel); $viewModel->setVariable('form', $form); return $viewModel; } /** this function called by indexAction to reduce complexity of function */ protected function authenticate($form, $viewModel) { $request = $this->getRequest(); if ($request->isPost()) { $form->setData($request->getPost()); if ($form->isValid()) { $dataform = $form->getData(); $this->authService->getAdapter() ->setIdentity($dataform['username']) ->setCredential($dataform['password']); $result = $this->authService->authenticate(); if ($result->isValid()) { //authentication success $resultRow = $this->authService->getAdapter()->getResultRowObject(); $this->authService->getStorage()->write( array('id' => $resultRow->id, 'username' => $dataform['username'], 'ip_address' => $this->getRequest()->getServer('REMOTE_ADDR'), 'user_agent' => $request->getServer('HTTP_USER_AGENT')) ); return $this->redirect()->toRoute('success', array('action' => 'index'));; } else { $viewModel->setVariable('error', 'Login Error'); } } } } public function logoutAction() { $this->authService->getStorage()->clear(); return $this->redirect()->toRoute('auth'); } }
f. AuthControllerServiceFactory
Controller creation need a factory, so we need to create a factory for it.
//filename : module/SanAuthWithDbSaveHandler/src/SanAuthWithDbSaveHandler/Factory/Controller/AuthControllerServiceFactory.php namespace SanAuthWithDbSaveHandler\Factory\Controller; use Zend\ServiceManager\FactoryInterface; use Zend\ServiceManager\ServiceLocatorInterface; use SanAuthWithDbSaveHandler\Controller\AuthController; class AuthControllerServiceFactory implements FactoryInterface { public function createService(ServiceLocatorInterface $serviceLocator) { $authService = $serviceLocator->getServiceLocator()->get('AuthService'); $controller = new AuthController($authService); return $controller; } }
g. SuccessController
it’s a page that be redirected from AuthController. I’ve made a test only, you can check in real application. Real application should has authService that injected by EventManger in Module::onBootstrap.
//filename : module/SanAuthWithDbSaveHandler/src/SanAuthWithDbSaveHandler/Controller/SuccessController.php namespace SanAuthWithDbSaveHandler\Controller; use Zend\Mvc\Controller\AbstractActionController; use Zend\Authentication\AuthenticationService; use Zend\View\Model\ViewModel; class SuccessController extends AbstractActionController { public function indexAction() { //here for test only, you should check session //for real application } }
h. login page
<?php //filename : module/SanAuthWithDbSaveHandler/view/san-auth-with-db-save-handler/auth/index.phtml $title = 'Login'; $this->headTitle($title); ?> <h1><?php echo $this->escapeHtml($title); ?></h1> <?php $form = $this->form; $form->setAttribute('action', $this->url( 'auth' )); $form->prepare(); echo $this->form()->openTag($form); echo $this->formCollection($form); echo $this->form()->closeTag(); echo $this->error;
i. success page
<?php //filename : module/SanAuthWithDbSaveHandler/view/san-auth-with-db-save-handler/success/index.phtml ?> login succeded. <a href="<?php echo $this->url('auth', array('action' => 'logout')); ?>">Logout</a>
3. Register services
We should register services at SanAuthWithDbSaveHandler/config/module.config.php
//filename : SanAuthWithDbSaveHandler/config/module.config.php namespace SanAuthWithDbSaveHandler; return array( //controllers services... 'controllers' => array( 'factories' => array( 'SanAuthWithDbSaveHandler\Controller\Auth' => 'SanAuthWithDbSaveHandler\Factory\Controller\AuthControllerServiceFactory' ), 'invokables' => array( 'SanAuthWithDbSaveHandler\Controller\Success' => 'SanAuthWithDbSaveHandler\Controller\SuccessController' ), ), //register auth services... 'service_manager' => array( 'factories' => array( 'AuthStorage' => 'SanAuthWithDbSaveHandler\Factory\Storage\AuthStorageFactory', 'AuthService' => 'SanAuthWithDbSaveHandler\Factory\Storage\AuthenticationServiceFactory', ), ), //routing configuration... 'router' => array( 'routes' => array( 'auth' => array( 'type' => 'segment', 'options' => array( 'route' => '/auth[/:action]', 'defaults' => array( 'controller' => 'SanAuthWithDbSaveHandler\Controller\Auth', 'action' => 'index', ), ), ), 'success' => array( 'type' => 'segment', 'options' => array( 'route' => '/success[/:action]', 'defaults' => array( 'controller' => 'SanAuthWithDbSaveHandler\Controller\Success', 'action' => 'index', ), ), ), ), ), //setting up view_manager 'view_manager' => array( 'template_path_stack' => array( 'SanAuthWithDbSaveHandler' => __DIR__ . '/../view', ), ), );
4. Last but not least, a Module Class ! (It’s very important! haha 😀 )
//filename : SanAuthWithDbSaveHandler/Module.php namespace SanAuthWithDbSaveHandler; class Module { public function getConfig() { return include __DIR__ . '/config/module.config.php'; } public function getAutoloaderConfig() { return array( 'Zend\Loader\StandardAutoloader' => array( 'namespaces' => array( __NAMESPACE__ => __DIR__ . '/src/' . str_replace('\\', '/' , __NAMESPACE__), ), ), ); } }
Now, try to login to application with url : http://YourZF2app/auth with username : admin and password : admin.
Done !
btw, I’ve uploaded source to my github account : https://github.com/samsonasik/SanAuthWithDbSaveHandler
References :
1. https://zf2.readthedocs.org/en/latest/modules/zend.session.save-handler.html
2. https://zf2.readthedocs.org/en/latest/modules/zend.authentication.intro.html
3. https://github.com/cgmartin/ZF2FileUploadExamples/blob/master/src/ZF2FileUploadExamples/Form/CollectionUpload.php
Fine post. I’ve been wrapping my head around ZF2 auth/acl/sessions components, so this helps a lot. And can’t thank you enough for your other posts on ZF2 — as there’s still not enough docs available on the framework’s website. I hope you’ll keep sharing with us and best of luck for you!
Thank You. Ameen.
Very useful post, as always! Thank you very much!
I have a question though. In my case system is now using two handlers. Native one and this one. For example if I manually delete the session row from table, my users are still staying logged in because php is using it’s default handler. If I remove the session file, users log out.
Do you know why it’s that?
oh I got it now. The topic is not about setting default session handler it’s about auth service and session db save handler. So my question is wrong. P.S: I successfully resolved my issue by myself!
great!
Hello samsonasik! I hasten to say a big thank you for every article you’ve written about Zend Framework 2 work you have done. Each topic has helped me understand the structure and arrangement of the ZF2. But I came across a problem at what point of the code, to check whether the user is authenticated, and if not, redirect him to a mandatory authentication. Even if the resource does not exist.
Alex Taran, with Best Regards.
Hello again, I understanding how to check this one, but i didn’t know how to attach this check in Module.php file. I tried to attach some function in onBootstrap but i everytime have a problem with with error: could not find ServiceLocator.
You did the right thing! I do not want to authenticated users logged in without seeing a 404 error, even if the page does not exist. So I’m a little cut your code. Thank you very much for the great help!
public function checkSession(MvcEvent $e)
{
$sm = $e->getApplication()->getServiceManager();
if ( ! $sm->get(‘AuthService’)->getStorage()->getSessionManager()
->getSaveHandler()
->read($sm->get(‘AuthService’)->getStorage()->getSessionId())) {
if ($e->getRouteMatch()->getParam(‘controller’) != ‘SanAuthWithDbSaveHandler\Controller\Auth’) {
$e->getRouteMatch()->setParam(‘controller’, ‘SanAuthWithDbSaveHandler\Controller\Auth’);
}
}
}
great!
Hi
Great tutorial
Any ideas how to unserialize the data in your sessions table
Thanks
Salam Alaykom . I want to thank you for you’r great Blog it was very helpful for me as begenner on ZF2 , i started to create my project and i used this authentification module that you create , just i whant to add some user on the “Users” table but when i try to login with other user that i create i had alwas alwas wrong password message . I thinck that the probleme’s the convetion to MD5 on the password row on My data base . i know that the solution’s simple but i m really blocked her . Great thanks
try to insert with :
and try to login with :
user_name : user
pass_word : user
thx It works with Mysgl , But when i try to insert from my console application the passwrod’s not converted on MD5 format on database and i can’t login with the username and password that create .
that’s your console problem, try to place script at file, at call that within mysql command.
This’s rhe Form
setAttribute(‘method’, ‘post’);
$this->add(array(
‘name’ => ‘id’,
‘type’ => ‘Hidden’,
));
$this->add(array(
‘name’ => ‘username’,
‘type’ => ‘Text’,
‘options’ => array(
‘label’ => ‘Nom’,
),
));
$this->add(array(
‘name’ => ‘password’,
‘type’ => ‘Password’,
‘options’ => array(
‘label’ => ‘Mot de passe’,
),
));
$this->add(
array(
‘name’ => ‘role’,
‘type’ => ‘Radio’,
‘attributes’ => array(
‘id’ => ‘type’
),
‘options’ => array(
‘label’ => ‘Role’,
‘value_options’ => array(
‘SuperAdmin’ => ‘SuperAdmin’,
‘Admin’ => ‘Admin’,
‘Utilisateur’ => ‘Utilisateur’
)
)
)
);
$this->add(array(
‘name’ => ’email’,
‘type’ => ‘Zend\Form\Element\Email’,
‘options’ => array(
‘label’ => ‘Email’,
),
));
$this->add(array(
‘name’ => ‘submit’,
‘type’ => ‘Submit’,
‘attributes’ => array(
‘value’ => ‘Go’,
‘id’ => ‘submitbutton’,
),
));
}
}
Model
id = (isset($data[‘id’])) ? $data[‘id’] : null;
$this->username = (isset($data[‘username’])) ? $data[‘username’] : null;
$this->password = (isset($data[‘password’])) ? $data[‘password’] : null;
$this->role = (isset($data[‘role’])) ? $data[‘role’] : null;
$this->email = (isset($data[’email’])) ? $data[’email’] : null;
}
public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new \Exception(“Not used”);
}
public function getInputFilter()
{
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$factory = new InputFactory();
$inputFilter->add($factory->createInput(array(
‘name’ => ‘id’,
‘required’ => true,
‘filters’ => array(
array(‘name’ => ‘Int’),
),
)));
$inputFilter->add($factory->createInput(array(
‘name’ => ‘username’,
‘required’ => true,
‘filters’ => array(
array(‘name’ => ‘StripTags’),
array(‘name’ => ‘StringTrim’),
),
‘validators’ => array(
array(
‘name’ => ‘StringLength’,
‘options’ => array(
‘encoding’ => ‘UTF-8’,
‘min’ => 1,
‘max’ => 100,
),
),
),
)));
$inputFilter->add($factory->createInput(array(
‘name’ => ‘password’,
‘required’ => true,
‘filters’ => array(
array(‘name’ => ‘StripTags’),
array(‘name’ => ‘StringTrim’),
),
‘validators’ => array(
array(
‘name’ => ‘StringLength’,
‘options’ => array(
‘encoding’ => ‘UTF-8’,
‘min’ => 1,
‘max’ => 100,
),
),
),
)));
$inputFilter->add($factory->createInput(array(
‘name’ => ‘role’,
‘required’ => true,
‘filters’ => array(
array(‘name’ => ‘StripTags’),
array(‘name’ => ‘StringTrim’),
),
‘validators’ => array(
array(
‘name’ => ‘StringLength’,
‘options’ => array(
‘encoding’ => ‘UTF-8’,
‘min’ => 1,
‘max’ => 100,
),
),
),
)));
$inputFilter->add($factory->createInput(array(
‘name’ => ’email’,
‘required’ => true,
‘filters’ => array(
array(‘name’ => ‘StripTags’),
array(‘name’ => ‘StringTrim’),
),
‘validators’ => array(
array(
‘name’ => ‘StringLength’,
‘options’ => array(
‘encoding’ => ‘UTF-8’,
‘min’ => 1,
‘max’ => 100,
),
),
),
)));
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
public function getArrayCopy()
{
return get_object_vars($this);
}
}
Thx for you’r , i use this part of code for authentification and i add other field for database like “role” …. , o want to ask you how can i make a secific root for any member of user , for example if the user’s Admin when he connect he will be redurected automaticly to root “Admin” and if the user’s “visitor” he will be redirected to root “visitor” ???
When u use “$this->identity()” in View
it come with an error
No AuthenticationService instance provided
any idea why
thank you
$this->identity() view helper find service naming ‘Zend\Authentication\AuthenticationService’, so you should change ‘AuthService’ service with that :
and change the AuthControllerServiceFactory :
Thank you for answer
I found fast way, Just we need to
‘service_manager’ => array(
‘aliases’ => array(
‘Zend\Authentication\AuthenticationService’ => ‘AuthService’,
),
),
It work fine with me
My new Q now is:
How can i make this Auth work fine with Subdomain
I added in Config
‘session’ => array(
‘name’ => ‘my_namespace’,
‘use_cookies’ => true,
‘cookie_domain’ => ‘.YourZF2app ‘,
‘remember_me_seconds’ => 2419200,
‘cookie_httponly’ => true,
‘cookie_path’ => ‘/’,
‘cookie_lifetime’ => 2449,
‘gc_maxlifetime’ => 2449,
),
and in AuthStorage Class
$config = $this->getServiceLocator()->get(‘Config’);
$sessionConfig->setOptions($config[‘session’]);
try
$saveHandler->open($sessionConfig->getOption(‘save_path’), $this->namespace);
and Also
$saveHandler->open($sessionConfig->getOption(‘cookie_path’), $this->namespace);
Not work with subdomain
Any idea how can i do that
Thank you man for this code
Hi again
My Fast trick is
ini_set(‘session.cookie_path’, ‘/’);
ini_set(‘session.cookie_domain’, ‘.zf2tutor.dev’);
but it doesn’t update modified column in database .
you can do something like this at AuthStorage::__construct
Hi there,
I get the following error as soon as I insert ‘Auth’ into the application.config.php
Fatal error: Uncaught exception ‘Zend\ModuleManager\Exception\RuntimeException’ with message ‘Module (Auth) could not be initialized.’ in C:\DefaultWorkspace\skeletonAuth\vendor\ZF2\library\Zend\ModuleManager\ModuleManager.php:175 Stack trace: #0 C:\DefaultWorkspace\skeletonAuth\vendor\ZF2\library\Zend\ModuleManager\ModuleManager.php(149): Zend\ModuleManager\ModuleManager->loadModuleByName(Object(Zend\ModuleManager\ModuleEvent)) #1 C:\DefaultWorkspace\skeletonAuth\vendor\ZF2\library\Zend\ModuleManager\ModuleManager.php(90): Zend\ModuleManager\ModuleManager->loadModule(‘Auth’) #2 [internal function]: Zend\ModuleManager\ModuleManager->onLoadModules(Object(Zend\ModuleManager\ModuleEvent)) #3 C:\DefaultWorkspace\skeletonAuth\vendor\ZF2\library\Zend\EventManager\EventManager.php(468): call_user_func(Array, Object(Zend\ModuleManager\ModuleEvent)) #4 C: in C:\DefaultWorkspace\skeletonAuth\vendor\ZF2\library\Zend\ModuleManager\ModuleManager.php on line 175
Best Regards
the module named SanAuthWithDbSaveHandler
great, thanks!
you’re welcome 😉
Hi there,
Sorry to bother you for an following issue.
Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for Zend\Db\Adapter\Adapter
Could please help me to sort out this error?
I have uploaded this screen capture to the URL https://docs.google.com/file/d/0B2owybOlptnxVGR6NXQwQXA5dE0/edit with public access but not sure if you can view it or not
Composer.json snapshot
“require”: {
“php”: “>=5.3.3”,
“zendframework/zendframework”: “=2.2.1”,
“doctrine/doctrine-orm-module”: “0.7.*”,
“zendframework/zend-developer-tools”: “dev-master”,
“zendframework/zftool”: “dev-master”,
“zf-commons/zfc-user-doctrine-orm”: “dev-master”
}
you must register your Zend\Db\Adapter\Adapter service. read the docs : http://zf2.readthedocs.org/en/latest/user-guide/database-and-models.html
Many Many Thanks!!!….. It worked as wonder 🙂 Thanks for the prompt answer.
you’re welcome 😉
I have one more query… how can I access session across all the controller?
I added following code in Success Controller but it gives fatal error saying that 1st argument (of following connstructor ) must be an instance of AuthenticationService
class SuccessController extends AbstractActionController
{
protected $authService;
//we will inject authService via factory
public function __construct(AuthenticationService $authService)
{
$this->authService = $authService;
}
……
I also tried following module.config.php but still error is there
‘controllers’ => array(
‘factories’ => array(
‘SanAuthWithDbSaveHandler\Controller\Auth’ => ‘SanAuthWithDbSaveHandler\Factory\Controller\AuthControllerServiceFactory’,
‘SanAuthWithDbSaveHandler\Controller\Success’ => ‘SanAuthWithDbSaveHandler\Factory\Controller\AuthControllerServiceFactory’
),
It might be a very silly question but I am working on Zend framework for first time so please bear with me
I also want to use this session in other controller like album controller and redirect user to login page if not logged in.
Thanks for the help.
use eventmanager that attach to ‘route’ event. Read this https://samsonasik.wordpress.com/2012/08/23/zend-framework-2-controllerpluginmanager-append-controller-pluginto-all-controller
Hi,
This has helped me to achieve the desired result…. Thanks a bunch!!! 🙂
You’re welcome 😉
Great Thanks for you’r worck , i’m using you’rModule and all works fine . Just i m confuse how can i test if the user is logged in to use my application and if he’s not he will redirect automaticly to Auth interface . Now on my Application any one write the url on the browser can acced to the specific page even he’s not logged in , I used ZfcUser for other application and for this need i used this function
if ($this->zfcUserAuthentication()->hasIdentity()) {
//……//
}
else
return $this->redirect()->toRoute(‘zfcuser’);
is there any similar condition on you’r Module .
great thx for this blog .
just create controller plugin like ZfcUser do like this https://github.com/ZF-Commons/ZfcUser/blob/master/src/ZfcUser/Controller/Plugin/ZfcUserAuthentication.php . Configure like at https://github.com/ZF-Commons/ZfcUser/blob/master/Module.php#L35 and run 😉
Hi there,
Thanks for this very interesting article.
As I mentioned on Twitter, I disagree a bit on the controller’s part of the code for the following reasons :
* Controller authentication’s code is tight coupled to the underlying auth adapter.
* Code intents is a bit complex and not that clear (more on this after).
After reading the code, I spotted 5 use of the Auth service in the controller :
* Checking if there is an identity.
* Attempt to login.
* Retrieve result row if success.
* Store identity data.
* Logout.
To achieve this 5 goals, you used 16 method calls to the service and his dependencies : it’s a bit difficult to read, understand and maintain.
I know it’s a bit out of the article’s scope, but I suggest implementing a really simple Facade pattern to clarify code intents and make the authentication easier to use. This Facade rely on a really simple interface of 4 methods :
* login, that attempt to authenticate, and return the result row if succeed.
* logout, that clear the login data.
* hasIdentity, to check if a user is logged in or not.
* storeIdentity, to save the data for later use.
That way, low level authentication things can be refactored without hurting the application.
A bit of code is better than a huge comment, so I forked your repo and commit my refactoring attempt : https://github.com/lucascorbeaux/SanAuthWithDbSaveHandler/commit/16229327d3ade4f80ff569c7ea3e1a40d118e79f
It’s far from perfect as I don’t master ZF2 structure, the naming is not great too and there is no comments, but it gives the big picture.
My two cents 🙂
Thank you very much. Btw, we should make the $result freely to decide where it to be placed, because we maybe need a $result->’getCode()’ to show to be catched, and redirect/fill message to the user. Feel free to make a PR, then I will review, and more than happy to merge it 😉
Thank you for your reply, as suggested the PR has been sent.
You’re right for the result, I’m not fan of this login method that authenticate and return the result row, as it’s not really extensible : I apply here a YAGNI approach to stick only to the need of the tutorial. Sure in a real world application this wouldn’t work for a long time.
Ok, merged, thanks!
what am I missing, does this example actually store the session data in the database as well? Or is this purely an authentication mechanism? I have tested the code and my session data is not in the session table.
just work at my side, please update your zf2 version to use latest master 😉
strange, I am using latest master (2 days old)
Check your web server error log
Hello,
Thanks for your blog. Please write me how can I connect acl to this ?
Really great tutorial. I have tried this. Working fine, session got saved to the db. But it is overwritten in a single row, even though I try different credentials.
write your custom code for your specific need 🙂
Hello,
Thanks for your blog. Please i have probleme __construct() in my controller :
Catchable fatal error: Argument 1 passed to Auth\Controller\AuthController::__construct() must be an instance of Zend\Authentication\AuthenticationService, none given, called in C:\wamp\www\Zf2\vendor\zendframework\zendframework\library\Zend\ServiceManager\AbstractPluginManager.php on line 170 and defined in C:\wamp\www\Zf2\module\Auth\src\Auth\Controller\AuthController.php on line 19
you’re not follow the step by step :p. your controller is not created via factory 😛
I got this error when I had my controller specified in both the ‘factories’ and ‘invokables’ sub sections of the ‘controllers’ section of the ‘module.config.php’ file. It should either be in one or the other. I think the controller was being invoked directly rather than via the factory so the constructor argument it expected wasn’t there.
I’ve the same problem
Hello,
How would you use the session save to the database in another module?
Many thanks for you blogs.. It has helped me at various points during development.
I need to discuss about an issue I found. The functionality registering the save handler with the session manager is to automatically save the contents into the DB, if I am not mistaken. But I have been using a very similar AuthStorage implementation (except the explicit calls in write() and clear()) in my code and it does not save it automatically. When I explicitly set the manager in the constructor (by passing the session manager object), and it saves it automatically to the DB (without your calls in write() and clear()). And I think that is how it should be. Once you set the save handler in the container’s manager (either session storage or otherwise), the manager should take care of writing to the storage (_SESSION if supported) and persisting it in the DB.
yes, but when authenticate() function of Authentication service got valid, it automatically save the identity to a session, I prevent it to expose more data ( ip address, role_id, and more). If you have any suggestion, please provide me a suggestion code for it ( as gist, pastebin, etc). Thank you.
Thanks very much for the good work you do for the ZF2 community. I always read your blog and get great ideas and inspiration from your articles. I have a question for you, I have a few apps in a Zend Developer Cloud container, I want to provide separate authentication for each app within the container, but it seems like the auth cookie is the same regardless of the folder in which the apps are. I understand this to be totally normal, while I need to change this behavior. I think that modifying the cookie to act as a dictionary collection that would store both, the id of the authenticated user and the app name, so that by changing the hasIdentity() method, it would check for the combination of both pieces of data before determining the user is authenticated or not before returning true or false.
Is this the right way to go? could you please suggest any pointers on this regard? It is ok to copy the source file for AuthenticationService, do the mods and mark the changes as overrides? Is there any better way to do this or achieve the same result? Thanks very much in advance.
you can modify the session save path :
and of course, change the session namespace from “my_storage_namespace” ( I provide ) to other name 😉
Good post about zend session, find helpful post to know more about zend session.
Hi, i want to do a select before and from this write the results in the session storage but when i do it write empty in my var sessions.
The select its okay, its only when i try to get the vars in the action.
¿How can pass the select to another action and get the result of the select?
Please Help Me 😥
I ❤ your posts.
Hi very nice work, but i’m facing a problem, when the session expires the aplication is not redirecting to the Authentication page, i’m getting 404 The requested controller could not be mapped to an existing controller class. because it’s appending the base path:
Controller:
Application\Controller\SanAuthWithDbSaveHandler\Controller\Auth(resolves to invalid controller class or alias: Application\Controller\SanAuthWithDbSaveHandler\Controller\Auth)
Can you help ?
[…] https://samsonasik.wordpress.com/2013/05/29/zend-framework-2-working-with-authenticationservice-and-d… […]
This was such a great little tutorial. I kept trying to use separate Session and Auth storage handlers and utterly frustrated that my sessions weren’t persisting! This helped me understand the Zend 2 system much better. Thank you.
Can you tell me how can i fetch more than one records from the database using zend db 2.3 ???
I am using the below code for fetching the records from the database but it didn’t give me the records
$sql = new Sql($adapter);
$select = $sql->select();
$select->from(‘nf_client’);
$select->where(array(‘client_id’ => $_GET[‘client_id’]));
$selectString = $sql->getSqlStringForSqlObject($select);
$results = $adapter->query($selectString, $adapter::QUERY_MODE_EXECUTE);
loop your $results
How ???
do effort please 😉
Give me the hints of looping the $results
that’s only hint, honestly, you need to learn php basic before use framework 🙂
you don’t worry i know this loop but how can i use loop when it doesn’t give me a single row.If this give me a single row then i use the loop.
we can use this loop when the result is in associative array!!! but there is no array of data it just count the number of row
Thanx a lot it gives the expected result. I was thinking that it gives a simple array but i was wrong it give the result as ArrayObject. thanx once again:)
If i want left join then what should i do???
please read the docs and do effort, I’m not a helpdesk
ok!!! don’t angry:)
[…] AuthenticationService and Session Db […]
Hi there! First of all, amazing tutorial. It’s very useful, hope ZF2 docs would be like this.
After authenticate successfully, i am redirected to the “success page” but if i visit the login page again, it happens:
An exception was raised while creating “AuthService”; no instance returned
Then after a quick refresh, this problem is “autosolved”, like nothing happen.
check error stack trace, you should got the reason.
I don’t know what just happened, but i’m not getting this error anymore.
Thanks by the answer! Hope it doesn’t happen again.
Also, thanks by everything.
I have the following code to insert data into the database using zend db 2.3
$adapter = new Zend\Db\Adapter\Adapter(array(
‘driver’ => ‘Mysqli’,
‘database’ => ‘nf_accounts’,
‘username’ => ‘root’,
‘password’ => ”
));
$gross_total=array_sum($_POST[‘price’]);
$sql = new Insert($adapter);
$sql->into(‘nf_invoices’);
$sql->columns(array(‘invoice_no’,’client_id’,’createdby’,’created_datetime’));
$sql->values(array(‘invoice_no’=>$_POST[‘invoice_no’],
‘client_id’=>$_POST[‘client_list’],
‘createdby’=>$_SESSION[‘DEAM_Username’],
‘created_datetime’=>date(“Y-m-d H:i:s”)),’set’);
but it is not insert data into the database without any error.
neither it insert data into the database nor gives any error
Hi Abdul. First of all, thank you so much for your help.
I used the “AuthenticationService and Session Db Save Handler” approach in my authentication module.
This works fine. Users are redirected to the login whenever there is no session. However I have a conflict with the authorization module. They both happen in the route event. Authentication has a higher priority. If The authorization module is enabled the redirect do not work. However when I change the redirect mechanic to the one shown here (#2) the redirect works fine.
I wish I can make this redirect works within the MVC. Don’t like the idea of setting and sending the response back manually.
This is current (approach # 1)
if(!$sm->get(‘AuthService’)->getStorage()->getSessionManager()->getSaveHandler()->read($sm->get(‘AuthService’)->getStorage()->getSessionId())) {
// Redirect to Login
$e->getRouteMatch()->setParam(‘controller’, ‘login’)->setParam(‘action’, ‘index’);
}
approach # 2
if(!$sm->get(‘AuthService’)->getStorage()->getSessionManager()->getSaveHandler()->read($sm->get(‘AuthService’)->getStorage()->getSessionId())) {
// Redirect to Login
$url = $e->getRouter()->assemble(array(‘action’ => ‘index’), array(‘name’ => ‘login’));
$response = $e->getResponse();
$response->getHeaders()->addHeaderLine(‘Location’, $url);
$response->setStatusCode(302);
$response->sendHeaders();
exit;
}
Any suggestion of what to check for to make approach # 1 work?.
I am not sure if has something to do with the fact that the controller and action
are changed in approach #1 (when do “set”) but not the route name. I have that route name in the white list..which means “do not even bother to check authentication or authorization” but I haven’t be able to override the route name.
Thanks very much in advance.
hm.., have you apply it in the ‘dispatch’ event with higher priority ?
I’m gone to inform my little brother, that he should also pay a visit this blog on regular basis to obtain updated from most
up-to-date information.
[…] AuthenticationService and Session Db […]
how can I access identity in models without using methods to create service instance, its not working when called in another service ?
inject it, please read my service manager cheat sheet https://samsonasik.wordpress.com/2013/01/02/zend-framework-2-cheat-sheet-service-manager/
HI Sam, as always you have great posts on zf2. I have one question: How can I use this authentication in a single sign on case? I have session id and session stored in DB, but when I send a request to another domain I tried to load the session using:
if($sid){
$session->destroy();
$session->setId($sid);
$session->start();
$session->regenerateId();
var_dump($sid);
die();
// session_id($sid);
}
but it redirects me to the login page. Any help will be great.
different domain *maybe* different session save_path, try to change it in same location.
I have sesssion in DB actually, so different domain share the same session table. I found the solution by setting the cookie with setcookie function.
Is there a way to print the select??
not sure what you asked is related with post, maybe you can take a look https://samsonasik.wordpress.com/2014/05/16/zend-framework-2-getting-real-sql-string-of-zend-db/
Can anyone please help on the error i am getting. It is related to Zend and sessions storage in dbase. We are migrating the database from MYSQL to MSSQL and our homepage has started showing zend errors. Error details are pasted below:
Fatal error: Uncaught exception ‘InvalidArgumentException’ with message ‘Mcd_Core_Form_CSRF::__construct Invalid ident passed’ in /home/dkapoor/projects/mcd-dbmigration/library/Zend/Controller/Plugin/Broker.php on line 334
( ! ) InvalidArgumentException: Mcd_Core_Form_CSRF::__construct Invalid ident passed in /home/dkapoor/projects/mcd-dbmigration/library/Mcd/Core/Form/CSRF.php on line 16
Zend_Controller_Exception: Mcd_Core_Form_CSRF::__construct Invalid ident passed#0 /home/dkapoor/projects/mcd-dbmigration/library/Mcd/Core/Form/Element/Hash.php(10): Mcd_Core_Form_CSRF->__construct(”) #1 /home/dkapoor/projects/mcd-dbmigration/library/Mcd/Core/Form.php(64): Mcd_Core_Form_Element_Hash->__construct(‘csrf’) #2 /home/dkapoor/projects/mcd-dbmigration/application/controllers/AuthentificationController.php(12): Mcd_Core_Form->__construct() #3 /home/dkapoor/projects/mcd-dbmigration/library/Zend/Controller/Action.php(516): AuthentificationController->loginAction() #4 /home/dkapoor/projects/mcd-dbmigration/library/Zend/Controller/Dispatcher/Standard.php(308): Zend_Controller_Action->dispatch(‘loginAction’) #5 /home/dkapoor/projects/mcd-dbmigration/library/Zend/Controller/Front.php(954): Zend_Controller_Dispatcher_Standard->dispatch(Object(Zend_Controller_Request_Http), Object(Zend_Controller_Response_Http)) #6 /home/dkapoor/projects/mcd-dbmigration/library/Zend/Application/Bootstrap/Bootstrap.php(105): Zend_Controller_Front->dispatch() #7 /home/dkapoor/projects/mcd-dbmigration/library/Zend/Application.php(382): Zend_Application_Bootstrap_Bootstrap->run() #8 /home/dkapoor/projects/mcd-dbmigration/www/index.php(54): Zend_Application->run() #9 {main} in /home/dkapoor/projects/mcd-dbmigration/library/Zend/Controller/Plugin/Broker.php on line 334
you’re using ZF1, nothing to do with ZF2, so I don’t think I can help
Hi, samsonasik , thanks for your post. I have seen sometimes session is lost when i close web browser or when i reset my pc and session keeps stored in session table. Sometimes i can see the web browser changes the session id. For example when i am log in session is this PHPSESSID =tgg3fd1totdj64kmmjjiouc5hasl92, but web browser change it in sometimes
hi samsonasik
help me please – How to exclude a controller action from this authentication
probably what you meant is authorization part, you may call `$e->getRouteMatch()->getParam(‘action’)` check, check the following sample: https://github.com/samsonasik/SanAuthWithDbSaveHandler/blob/master/Module.php#L15-L35
Thank you so much, It worked and here is the method I used:
public function checkAuthenticated(MvcEvent $e)
{
if (!$this->isOpenRequest($e)) {
$sm = $e->getApplication()->getServiceManager();
if (!$sm->get(‘AuthService’)->getStorage()->getSessionManager()
->getSaveHandler()->read($sm->get(‘AuthService’)->getStorage()->getSessionId())
) {
$e->getRouteMatch()
->setParam(‘controller’, ‘Users\Controller\Auth’)
->setParam(‘action’, ‘index’);
}
}
}
public function isOpenRequest(MvcEvent $e)
{
if ($e->getRouteMatch()->getParam(‘controller’) == ‘Users\Controller\AuthController’ || $e->getRouteMatch()->getParam(‘controller’)==’MyAction\Controller\Action’) {
return true;
}
return false;
}
hi samsonasik,
In the AuthController is there any difference between using:
if ($this->authService->getStorage()->getSessionManager()
->getSaveHandler()
->read($this->authService->getStorage()->getSessionId()))
{…}
or:
if ($this->authService->hasIdentity()) {…}
?
please read the `AuthStorage::write($contents)`, I need only save to DB when the passed parameter $contents is an array, and I need to validate if the passed parameter is the array (as we use on `AuthController::authenticate()` ), we actually need that to ensure if the identity is an array of passed in authenticate before. The hasIdentity() only check if there is value in session storage.
Ok now I get it. But it still works even if the json array is empty.
Do you think it could be a good idea to refine the check this way?
$identity=$this->ZendAuthService->getStorage()->getSessionManager()->getSaveHandler()->read($this->ZendAuthService->getStorage()->getSessionId());
if(json_decode($identity,1) && json_last_error() === JSON_ERROR_NONE) {…}
boolean comparison is enough for empty array check https://3v4l.org/7EB66 . If you found another use case, fill free to do so
Yeah it’s just because the json format.
How this will work with remember me feature ?
you can read https://samsonasik.wordpress.com/2012/10/23/zend-framework-2-create-login-authentication-using-authenticationservice-with-rememberme/