Zend Framework 3 / Laminas Doctrine Authentication Module
This module requires Doctrine Module and uses Doctrine ORM.
I also assume that you are familiar with Doctrine ORM, Doctrine Module, Zend Framework / Laminas and using the command line interface.
Table of contents- Installation
- Post installation
- Configuration
- Extending the users entity
- Updating your module.config.php
- Database Setup
- Extending the registration form
- Changing the view scripts
- Adding custom code to registration
- Adding custom code to login
- Laminas Navigation intergration
- Routing
- Ajax and JSON
- Updating doctrine-auth module
- Make a donation
Installation Return to top
With Composer Return to top
-
In the root of your application enter:
$ composer require krytenuk/doctrine-auth
Post installation Return to top
-
Enable it in your `application.config.php` or `modules.config.php` file.
For ZF2 edit your `application.config.php` file.
<?php
return array(
'modules' => array(
// ...
'FwsDoctrineAuth',
),
// ...
);
For ZF3/Laminas edit your `modules.config.php` file.
<?php
return [
'FwsDoctrineAuth',
// ...
]; - Copy `./vendor/krytenuk/doctrine-auth/config/doctrine.auth.config.local.php.dist` to `./config/autoload/doctrine.auth.config.local.php`.
- Copy `./vendor/krytenuk/doctrine-auth/config/doctrine.auth.acl.local.php.dist` to `./config/autoload/doctrine.auth.acl.local.php`.
Configuration Return to top
In your ./config/autoload/doctrine.auth.config.local.php
-
Under the ['doctrine']['connection'] key you need to setup your database connection
Key Type Description Default Value host string Your database host localhost port integer Port used to connect to database 3306 dbname string Name of the database user string Database user password string Database user password
- Key
- host
- Type
- string
- Description
- Your database host
- Default Value
- localhost
- Key
- port
- Type
- integer
- Description
- Port used to connect to database
- Default Value
- 3306
- Key
- dbname
- Type
- string
- Description
- Name of the database
- Default Value
- Key
- user
- Type
- string
- Description
- Database user
- Default Value
- Key
- password
- Type
- string
- Description
- Database user password
- Default Value
-
Under the ['doctrine']['authentication'] key you need to setup your users entity
Key Type Description Default Value identity_class string This is the Doctrine users entity \FwsDoctrineAuth\Entity\BaseUsers identity_property string The column in the users entity in which to search e.g. username, emailAddress
If you change this you must add the column to your users entity, this column must also be uniqueemailAddress credential_property string The users password column password
- Key
- identity_class
- Type
- string
- Description
- This is the Doctrine users entity
- Default Value
- \FwsDoctrineAuth\Entity\BaseUsers
- Key
- identity_property
- Type
- string
- Description
- The column in the users entity in which to search e.g. username, emailAddress
If you change this you must add the column to your users entity, this column must also be unique - Default Value
- emailAddress
- Key
- credential_property
- Type
- string
- Description
- The users password column
- Default Value
- password
-
Under the ['doctrineAuth'] key you need set other values
Key Type Description Default Value registrationForm string The form to use when registering a new user \FwsDoctrineAuth\Form\RegisterForm::class loginForm string The form to use when logging in a user \FwsDoctrineAuth\Form\LoginForm::class formElements array identity_label string The identity label to use in the login and registration forms Email Address credential_label string The credential label to use in the login and registration forms Password allowRegistration boolean Allow new user registration (TRUE or FALSE) TRUE registrationCallback string Callback class to add custom registration code loginCallback string Callback class to add custom login code autoRegistrationLogin boolean Allowe auto login after registration (TRUE or FALSE) FALSE userActiveAfterRegistration integer User active after registration (1 = active, 0 = inactive) 1 siteName string The title of your website, used in password reset email Example Site emailResetLinkForm string The form to use when resetting password \FwsDoctrineAuth\Form\EmailForm::class newPasswordForm string The form to use when entering a new password (from emailed link) \FwsDoctrineAuth\Form\ResetPasswordForm::class allowPasswordReset boolean Show reset password link on login form and allow password reset (TRUE or FALSE) TRUE passwordLinkActiveFor integer Password reset link active for hours 24 fromEmail string Email address for password reset link email, set reply to and from email no-reply@example.com
- Key
- registrationForm
- Type
- string
- Description
- The form to use when registering a new user
- Default Value
- \FwsDoctrineAuth\Form\RegisterForm::class
- Key
- loginForm
- Type
- string
- Description
- The form to use when logging in a user
- Default Value
- \FwsDoctrineAuth\Form\LoginForm::class
- Key
- formElements
- Type
- array
- Key
- identity_label
- Type
- string
- Description
- The identity label to use in the login and registration forms
- Default Value
- Email Address
- Key
- credential_label
- Type
- string
- Description
- The credential label to use in the login and registration forms
- Default Value
- Password
- Key
- allowRegistration
- Type
- boolean
- Description
- Allow new user registration (TRUE or FALSE)
- Default Value
- TRUE
- Key
- registrationCallback
- Type
- string
- Description
- Callback class to add custom login code
- Default Value
- Key
- loginCallback
- Type
- string
- Description
- Callback class to add custom registration code
- Default Value
- Key
- autoRegistrationLogin
- Type
- boolean
- Description
- Allowe auto login after registration (TRUE or FALSE)
- Default Value
- FALSE
- Key
- userActiveAfterRegistration
- Type
- integer
- Description
- User active after registration (1 = active, 0 = inactive)
- Default Value
- 1
- Key
- siteName
- Type
- string
- Description
- The title of your website, used in password reset email
- Default Value
- Example Site
- Key
- emailResetLinkForm
- Type
- string
- Description
- The form to use when resetting password
- Default Value
- \FwsDoctrineAuth\Form\EmailForm::class
- Key
- newPasswordForm
- Type
- string
- Description
- The form to use when entering a new password (from emailed link)
- Default Value
- \FwsDoctrineAuth\Form\ResetPasswordForm::class
- Key
- allowPasswordReset
- Type
- boolean
- Description
- Show reset password link on login form and allow password reset (TRUE or FALSE)
- Default Value
- TRUE
- Key
- passwordLinkActiveFor
- Type
- integer
- Description
- Password reset link active for hours
- Default Value
- 24
- Key
- fromEmail
- Type
- string
- Description
- Email address for password reset link email, set reply to and from email
- Default Value
- no-reply@example.com
-
To setup laminas-session please see laminas-session
In your ./config/autoload/doctrine.auth.acl.local.php
This is the access control list config. Here you setup your user roles, rescources (modules, controllers) and which roles have access to which resources (permissions). This is the backbone of Doctrine Auth.
User roles Return to top
All users are assigned roles, guest being the default. Guest users can access only limited resources such as the login page. Once logged in a users role takes on the role that is assigned to them, usually by an admin user and can access other areas of the application (website) not accessable to guest users. Examples of user roles are, user, author and admin. User roles can also inheret permissions from their parent roles, for example if the admin role has the author role as it's parent it can access all the resources that the author can.
To setup your user roles
-
Under the ['doctrineAuthAcl']['roles'] key
Key Type Description id string The unique role identifier parents array The roles parent role(s) if any
Roles inheret access to resources from their parentsredirect array route string The route to redirect to after successful login
If the user is already trying to access a restricted rescource then redirect to that if allowed. If not then redirect to this route.params array The routes params, see laminas-router for more info options array The routes options, see laminas-router for more info
- Key
- id
- Type
- string
- Description
- The unique role identifier
- Key
- parents
- Type
- array
- Description
- The roles parent role(s) if any
Roles inheret access to resources from their parents
- Key
- redirect
- Type
- array
- Key
- route
- Type
- string
- Description
- The route to redirect to after successful login
If the user is already trying to access a restricted rescource then redirect to that if allowed. If not then redirect to this route.
- Key
- params
- Type
- array
- Description
- The routes params, see laminas-router for more info
- Key
- options
- Type
- array
- Description
- The routes options, see laminas-router for more info
-
Under the ['doctrineAuthAcl'] key
Key Type Description Default Value defaultRegisterRole string The default role assigned to a user after successful registration user
- Key
- defaultRegisterRole
- Type
- string
- Description
- The default role assigned to a user after successful registration
- Default Value
- user
Resources Return to top
Resources are basically the modules and controllers that makeup your application. Usually I create a module for all my user roles created above (guest is usually the "application" module), for example for an admin user role I would create an admin module. In here I will place all the controllers the admin role can access. You may also wish to create an auth module if you want to extend this "Doctrine Auth" module.
To set your resources
-
Under the ['doctrineAuthAcl']['resources'] key
Key Type Description module string Your module controllers array An array of your modules controllers fully qualifired names e.g. \namespace\Controller\YourController::class
- Key
- module
- Type
- string
- Description
- Your module
- Key
- controllers
- Type
- array
- Description
- An array of your modules controllers fully qualifired names e.g. \namespace\Controller\YourController::class
Permissions Return to top
Here we tell "Doctrine Auth" which user roles can access which resources set above. We can also specify individual actions within a controller.
To set your permissions
-
Under the ['doctrineAuthAcl']['permissions'] key
Key Type Description type \Laminas\Permissions\Acl\Acl::TYPE_ALLOW or
\Laminas\Permissions\Acl\Acl::TYPE_DENYSet permission type, either allow access or deny access to the module/controller specified below role string A user role id defined ealier resource string A previously defined resource, either a module or controller
If you specify a module then all the controllers under it inherit this permissionactions array If you specify a controller above then an array of actions can be specified
If left empty then this permission applies to all actions
- Key
- type
- Type
- \Laminas\Permissions\Acl\Acl::TYPE_ALLOW or
\Laminas\Permissions\Acl\Acl::TYPE_DENY - Description
- Set permission type, either allow access or deny access to the module/controller specified below
- Key
- role
- Type
- string
- Description
- A user role id defined ealier
- Key
- resource
- Type
- string
- Description
- A previously defined resource,
either a module or controller
If you specify a module then all the controllers under it inherit this permission
- Key
- actions
- Type
- array
- Description
- If you specify a controller above then an array of actions can be specified
If left empty then this permission applies to all actions
Other Settings Return to top
-
Under the ['doctrineAuthAcl'] key
Key Type Description Default injectAclIntoNavigation boolean Inject ACL into Laminas navigation view helper plugin (TRUE or FALSE)
This will only show the resources accessable to the current user when using Laminas NavigationTRUE
- Key
- injectAclIntoNavigation
- Type
- boolean
- Description
- Inject ACL into Laminas navigation
view helper plugin (TRUE or FALSE)
This will only show the resources accessable to the current user when using Laminas Navigation
Extending the users entity Return to top
As standard FWS Doctrine Auth ships with a base users entity, \FwsDoctrineAuth\Entity\BaseUsers, which works out of the box.
<?php
namespace FwsDoctrineAuth\Entity;
use Doctrine\ORM\Mapping as ORM;
use FwsDoctrineAuth\Entity\UserRoles;
use DateTime;
/**
* Users
* @ORM\Entity(repositoryClass="FwsDoctrineAuth\Entity\Repository\UserRepository")
* @ORM\Table(name="users", indexes={
* @ORM\Index(name="user_id", columns={"user_id"}),
* })
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="type", type="string")
* @author Garry Childs <info@freedomwebservices.net>
*/
class BaseUsers implements EntityInterface
{
/**
* @var integer
*
* @ORM\Column(name="user_id", type="integer", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $userId;
/**
* identity_property in config
* @var string
*
* @ORM\Column(name="email_address", type="string", length=60, nullable=true)
*/
private $emailAddress;
/**
* credential_property in config
* @var string
*
* @ORM\Column(name="password", type="string", length=60, nullable=false)
*/
private $password;
/**
* @var integer
*
* @ORM\Column(name="user_active", type="integer", nullable=false)
*/
private $userActive;
/**
* @var DateTime
*
* @ORM\Column(name="date_created", type="datetime")
*/
private $dateCreated;
/**
* @var DateTime
*
* @ORM\Column(name="date_modified", type="datetime")
*/
private $dateModified;
/**
* @var BaseUserRoles
*
* @ORM\ManyToOne(targetEntity="FwsDoctrineAuth\Entity\UserRoles")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="user_role_id", referencedColumnName="user_role_id")
* })
*/
private $userRole;
/* Getters & Setters */
However you may well need to add additional columns to this entity to suit your application. As such this entity is designed to be extended.
Below is an example of extending the users entity by adding a company name

<?php
namespace YourNamespace\Entity;
use FwsDoctrineAuth\Entity\BaseUsers;
use Doctrine\ORM\Mapping as ORM;
/**
* Users
* @ORM\Entity
*/
class Users extends BaseUsers
{
/**
* @var string
*
* @ORM\Column(name="company_name", type="string", length=255, nullable=true)
*/
private $companyName;
public function getCompanyName()
{
return $this->companyName;
}
public function setCompanyName($companyName)
{
$this->companyName = $companyName;
return $this;
}
}
Don't forget to update your identity_class in the authentication config
For more information on Doctrine Entities please visit here
Updating your module.config.php Return to top
In your module(s) where your doctrine entties are stored you must update your module.config.php file to tell doctrine where your entities are.

<?php
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
return [
'doctrine' => [
'driver' => [
__NAMESPACE__ . '_driver' => [
'class' => AnnotationDriver::class,
'paths' => [__DIR__ . '/../src/Entity'], // path to your entity directory
],
'orm_default' => [
'drivers' => [
__NAMESPACE__ . '\Entity' => __NAMESPACE__ . '_driver'
],
],
],
],
];
Database setup Return to top
In order to use FWS Doctrine Auth you need to setup your database through Doctrine Modules CLI
New database Return to top
Ensure that all your user roles and Doctrine entities have been created before creating your database
-
To create a new database in MySQL use.
mysql> CREATE DATABASE `your_database`;
mysql> CREATE USER `your_user`@`localhost` IDENTIFIED BY 'your_password';
mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, INDEX, DROP, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES ON `your_database`.* TO `your_user`@`localhost`; -
and to add the tables using the CLI.
$ vendor/bin/doctrine-module orm:schema-tool:create
Existing database Return to top
Ensure you backup your database first.
-
On an existing database use.
$ vendor/bin/doctrine-module orm:schema-tool:update --force
For more information on Doctrine CLI see Doctrine Module and Doctrine Console
Once your database tables are setup.
-
Populate the user roles
Ensure that you run this command after adding new user roles$ vendor/bin/doctrine-module doctrine-auth:init
-
you can also truncate the user roles table prior to populating
use this with caution$ vendor/bin/doctrine-module doctrine-auth:init --truncate
Extending the registration form Return to top
The registration form simply collects the users email address and password, you may wish to add more fields to populate your users entity
Firstly add the columns to your users entity. Create methods "addElements()" for your elements and "addInputFilterSpecification()" for your input filters to your registration form, then add the fields to your registration form. Both the form field name and entity variable must be the same for auto population to work. Ensure that your registration form extends the default \FwsDoctrineAuth\Form\RegisterForm

<?php
namespace Auth\Form;
use FwsDoctrineAuth\Form\RegisterForm AS FwsDoctrineAuthRegisterForm;
use FwsDoctrineAuth\Form\FormInterface;
use Laminas\Form\Element\Text;
use Laminas\Validator;
/**
* Description of Register
*
* @author Garry Childs <info@freedomwebservices.net>
*/
class RegisterForm extends FwsDoctrineAuthRegisterForm implements FormInterface
{
public function addElements()
{
$this->add([
'name' => 'companyName',
'type' => Text::class,
'attributes' => [
'size' => 16,
'maxlength' => 255,
],
'options' => [
'label' => _('Company'),
'label_attributes' => ['class' => 'required'],
],
]);
}
public function addInputFilterSpecification()
{
return [
'companyName' => [
'required' => FALSE,
'filters' => [
['name' => 'StripTags'],
['name' => 'StringTrim'],
],
'validators' => [
[
'name' => Validator\StringLength::class,
'options' => [
'encoding' => 'UTF-8',
'min' => 3,
'max' => $this->get('companyName')->getAttribute('maxlength'),
'messages' => [
Validator\StringLength::INVALID => _("Your company name must contain between %min% and %max% characters"),
Validator\StringLength::TOO_LONG => _("Your company name must not contain more than %max% characters"),
Validator\StringLength::TOO_SHORT => _("Your company name must contain more than %min% characters"),
],
],
],
],
],
];
}
}
Next add your form to the registrationForm key in doctrine.auth.config.local.php
And finally register your form in your module.config.php

<?php
namespace YourNamespace;
use YourNamespace\Form\RegisterForm;
use FwsDoctrineAuth\Form\Service\RegisterFormFactory;
return [
'form_elements' => [
'factories' => [
RegisterForm::class => RegisterFormFactory::class,
],
],
];
The login form can be extended in a similar fashon if required. Also the reset email form and reset password form can be extended, this is mainly to add to existing form elements rather than adding new elements. For example you can add attributes to the form elements.

<?php
namespace YourNamespace\Form;
use FwsDoctrineAuth\Form\EmailForm as FwsDoctrineAuthEmailForm;
class EmailForm extends FwsDoctrineAuthEmailForm
{
public function init()
{
parent::init();
$this->get($this->getIdentityName())->setAttribute('class', 'your-class');
}
}
Changing the view scripts Return to top
To override the default view scripts you must add the following to your module's module.config.php.
<?php
'view_manager' => array(
'template_path_stack' => array(
'your_module' => __DIR__ . '/../view',
),
'template_map' => array(
'fws-doctrine-auth/index/register' => __DIR__ . '/../view/your_module/your_folder/register.phtml', // override register form view
'fws-doctrine-auth/index/login' => __DIR__ . '/../view/your_module/your_folder/login.phtml', // override login form view
'fws-doctrine-auth/index/password-reset' => __DIR__ . '/../view/your_module/your_folder/password-reset.phtml', // override password reset view
'fws-doctrine-auth/emails/password-reset' => __DIR__ . '/../view/your_module/your_emails_folder/password-reset.phtml', // override password reset email
),
),
Refer to each built-in view script to see how the form is configured and rendered.
Here is an examle of a custom register form.
<h2><?php echo $this->translate('Custom Register Form'); ?></h2>
<?php
if ($this->errorMessage) {
echo '<p class="error">' . $this->errorMessage . '</p>' . PHP_EOL;
}
$this->form->setAttribute('action', $this->url('doctrine-auth/default', array('action' => 'register')));
$this->formElementErrors()->setAttributes(array('class' => 'errors'));
$this->form->prepare();
echo $this->form()->openTag($this->form);
?>
<dl class="zend_form">
<dt><?php echo $this->formLabel($this->form->get('companyName')); ?></dt>
<dd><?php echo $this->formText($this->form->get('companyName')) . $this->formElementErrors($this->form->get('companyName')); ?></dd>
<dt><?php echo $this->formLabel($this->form->get('emailAddress')); ?></dt>
<dd><?php echo $this->formText($this->form->get('emailAddress')) . $this->formElementErrors($this->form->get('emailAddress')); ?></dd>
<dt><?php echo $this->formLabel($this->form->get('password')); ?></dt>
<dd><?php echo $this->formPassword($this->form->get('password')) . $this->formElementErrors($this->form->get('password')); ?></dd>
<dd><?php echo $this->formHidden($this->form->get('csrf')) . $this->formElementErrors($this->form->get('csrf')); ?>
<?php echo $this->formSubmit($this->form->get('submit')); ?></dd>
</dl>
<?php echo $this->form()->closeTag(); ?>
Adding custom code to registration Return to top
Whilst adding fields to your registration form having the same name as the property in your users entity will auto populate the entity, there are times that you may need to add custom code to the registration process. There is a config key, registrationCallback , which enables you to specify an invokeable class that contains this code. An example can be seen below.

<?php
namespace Auth\Model;
use FwsDoctrineAuth\Entity\BaseUsers;
use Laminas\Form\FormInterface;
use Laminas\Stdlib\Parameters;
class Registration
{
public function __invoke(BaseUsers $userEntity, FormInterface $form, Parameters $postData)
{
// Your code here
}
}
Here you have access to the user entity, form and the post data from the browser. You don't need to return anything, after this the users entity is saved.
Adding custom code to login Return to top
You can also add custom code to your login form using the same method as with the registration. There is a config key loginCallback , which enables you to specify an invokeable class that contains this code. An example can be seen below.

<?php
namespace Auth\Model;
use FwsDoctrineAuth\Entity\BaseUsers;
use Laminas\Form\FormInterface;
class Login
{
public function __invoke(BaseUsers $userEntity, FormInterface $form, Array $data)
{
// Your code here
}
}
Here you have access to the user entity, form and the form data. You don't need to return anything, after this the users entity is saved.
Laminas Navigation intergration Return to top
Doctrine Auth comes with laminas-navigation intergration builtin. This simply injects the ACL into the Laminas Navigation view helper showing only the links to resources the current user is allowed to access in your navigation menus.
For example in your navigation config you can use

<?php
use FwsDoctrineAuth\Controller\IndexController;
use Application\Controller\IndexController as ApplicationIndexController;
return array(
'navigation' => array(
'default' => array(
array(
'label' => _('Home'),
'route' => 'application',
'resource' => ApplicationIndexController::class,
'privilege' => 'index',
),
array(
'label' => _('Login'),
'route' => 'doctrine-auth/default',
'action' => 'login',
'resource' => IndexController::class,
'privilege' => 'login',
),
array(
'label' => _('Logout'),
'route' => 'doctrine-auth/default',
'action' => 'logout',
'resource' => IndexController::class,
'privilege' => 'logout',
),
),
),
);
In your layout.phtml or a view.phtml script

<?= $this->navigation('Laminas\Navigation\Default')->menu(); ?>
By default when used in your layout.phtml or a view.phtml file and is viewed by a guest user then they will only see the login link as they are not allowed to access logout. However if an admin views this then all they will see is the logout link as they don't have permission to access login (they are already logged in!). Both users will see the home link.
Routing Return to top
Using one route for multiple controllers
In order to use the controller parameter to route to a multipule controllers from a single route, you need to add an aliases to map them to the fully qualified controller names. Below is an example of this, for example going to a url of www.yourdomain.com/user/somecontroller/some-action should goto a controller named \User\Controllers\somecontrollerController and dispatch the someActionAction() method.

<?php
namespace User;
use Laminas\ServiceManager\Factory\InvokableFactory;
use User\Controller;
use Laminas\Router\Http\Segment;
return [
'controllers' => array(
'aliases' => array(
'somecontroller' => Controller\SomecontrollerController::class, // controller route aliases here
),
'factories' => array(
Controller\SomecontrollerController::class => InvokableFactory::class,
),
),
'router' => [
'routes' => [
'user' => [
'type' => Segment::class,
'options' => [
'route' => '/user[/:controller[/:action]]',
'constraints' => [
'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
],
'defaults' => [
'controller' => Controller\IndexController::class,
'action' => 'index',
],
],
],
],
],
];
Ajax and JSON Return to top
If a user attempts an ajax request and gets logged out, doctrine auth will send a json response of redirect to the login url. You can catch this response in your js code.
Here's a jQuery example.

$.ajax({
method: 'POST',
url: '/your/url',
dataType: 'json',
processData: false,
contentType: false,
data: formData,
}).done(function (json) {
// your code here
if (json.redirect) {
window.location.replace(json.redirect); // force browser to login page
}
});
Updating Doctrine Auth module Return to top
This module should automatically update with a standard composer update. If this does not happen then simply execute the command below, this should detect the latest version and install. You may need to remove the krytenuk/doctrine-auth entry from your composer.json file first.

$ composer require krytenuk/doctrine-auth
When updating to a newer version of doctrine-auth module you may need to alter your database schema in order for the new version to work. To do this simply run the cli commands below in both your development and production servers, please backup your databases before doing the update command.
To check if the doctrine entities and database schema are in synch use

$ vendor/bin/doctrine-module orm:validate-schema
And to synchronize your database schema with the entities use

$ vendor/bin/doctrine-module orm:schema-tool:update --force