Laminas Doctrine Authentication Module
Doctrine Auth is a registration and authentication module for Laminas MVC Framework and Doctrine ORM. Although earlier versions work with Zend Framework 2, this branch is now no longer supported or maintained.
This module offers a login form for a user to enter their credentials and will be authenticated against a database table of users. This database table is designed to be extended like the rest of Doctrine Auth's code is highly customisable and flexible, I designed this to work out of the box also as well as integrate into existing websites.
As of version 0.3.0 Doctrine Auth offers two factor authentication (2FA) using any combination of email, SMS[1] or Google Authenticator app. Again this works out of the box but can also be customised. Along with this Doctrine Auth also offers RSA encryption along with the password encryption that’s already in place. This can keep your data secure[2] in the event of your database being leaked. There is also new command line commands to encrypt/decrypt your existing database tables but you will need to add your own code to encrypt your custom properties. Doctrine Auth ships with a Crypt model and trait and a decrypt view helper to simplify this process.
[1] SMS 2FA uses BulkSMS and as such you will need to sign-up and purchase credits to use this method.
[2] If you are using email addresses as your login identity property then these can not be encrypted due to the way Laminas authentication works. If you use a different identity property such as username then you can encrypt your users email addresses.
This module requires Doctrine Module and uses Doctrine ORM.
I also assume that you are familiar with Doctrine ORM, Doctrine Module, Laminas / Zend Framework and using the command line interface.
Table of contents- Installation
- Post installation
- Configuration
- Extending the users entity
- Updating your module.config.php
- Database Setup
- Using two factor authentication (new)
- Encrypting your database (new)
- Extending the standard forms
- Changing the view scripts
- Adding custom code to registration
- Adding custom code to login
- Laminas Navigation integration
- 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
Please check the config settings when updating doctrine auth as new settings can be added causing errors in your application
-
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 Misc settings siteName string The title of your website, used in email's Example Site siteCountryCode string The 2 character ISO 3166 Country Code of your website GB sendEmails boolean Send emails if true or create email file if false. Handy if development server is not setup to send emails. Remember to set true on production server true emailsFolder string Location to store email files relative to your application root, only used if the above "sendEmails" is set to false, remember to create folder(s) emails Encryption encryptData boolean Encrypt user entity data false rsaPrivateKeyFile string Path and filename of your rsa private key relative to your application root. See here for how to generate your key pair rsa/key.pem rsaPublicKeyFile string Path and filename of your rsa public key relative to your application root. See here for how to generate your key pair rsa/key.pub rsaKeyPassphrase string Optional passphrase used when creating the above keys passphrase Registration allowRegistration boolean Allow new users to register from the frontend true registrationForm string The form to use when registering a new user \FwsDoctrineAuth\Form\RegisterForm::class registrationCallback string Callback class to add custom registration code autoRegistrationLogin boolean Allow user to be automatically logged-in after registration false userActiveAfterRegistration integer User active after registration (1 = active, 0 = inactive). Inactive users can not login 1 Login loginForm string The form to use when logging in a user \FwsDoctrineAuth\Form\LoginForm::class loginCallback string Callback class to add custom login code maxLoginAttempts integer Maximum number of allowed login attempts before blocking IP address 3 maxLoginAttemptsTime integer Number of minuets user can make the above login attempts, by default a user can make 3 wrong attempts within a 5 minute period before becoming blocked 5 loginReleaseTime integer Number of minutes user remains blocked for, 0 = don't unblock. If used should be greater than the above maxLoginAttemptsTime 0 Password reset allowPasswordReset boolean Show reset password link on login form and allow password reset true 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 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 Form labels 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 Two factor authentication useTwoFactorAuthentication boolean Use two factor authentication false selectTwoFactorAuthMethodForm string Select authentication method during login form, appears after user enters password successfully \FwsDoctrineAuth\Form\SelectTwoFactorAuthMethodForm::class twoFactorAuthCodeForm string Enter authentication code form, appears after user selects authentication method from above form \FwsDoctrineAuth\Form\TwoFactorAuthCodeForm::class allowedTwoFactorAuthenticationMethods array Array of 2FA methods to use, \FwsDoctrineAuth\Model\TwoFactorAuthModel::EMAIL, \FwsDoctrineAuth\Model\TwoFactorAuthModel::SMS and/or \FwsDoctrineAuth\Model\TwoFactorAuthModel::GOOGLEAUTHENTICATOR [\FwsDoctrineAuth\Model\TwoFactorAuthModel::EMAIL] twoFactorCodeActiveFor integer 2FA auth code active time in minuets after generation, user must enter code before time expires 10 bulkSmsUsername string Your BulkSMS username, required for SMS codes yourUsername bulkSmsApiTokenId string Your BulkSMS API token id yourApiToken bulkSmsApiTokenSecret string Your BulkSMS API token secret yourApiTokenSecret
Misc settings
- Key
- siteName
- Type
- string
- Description
- The title of your website, used in password reset email
- Default Value
- Example Site
- Key
- siteCountryCode
- Type
- string
- Description
- The 2 character ISO 3166 Country Code of your website
- Default Value
- GB
- Key
- sendEmails
- Type
- boolean
- Description
- Send emails if true or create email file if false. Handy if development server is not setup to send emails. Remember to set true on production server
- Default Value
- true
- Key
- emailsFolder
- Type
- string
- Description
- Location to store email files relative to your application root, only used if the above "sendEmails" is set to false, remember to create folder(s)
- Default Value
- emails
Encryption
- Key
- encryptData
- Type
- boolean
- Description
- Encrypt user entity data
- Default Value
- false
- Key
- rsaPrivateKeyFile
- Type
- string
- Description
- Path and filename of your rsa private key relative to your application root. See here for how to generate your key pair
- Default Value
- rsa/key.pem
- Key
- rsaPublicKeyFile
- Type
- string
- Description
- Path and filename of your rsa public key relative to your application root. See here for how to generate your key pair
- Default Value
- rsa/key.pub
Registration
- Key
- allowRegistration
- Type
- boolean
- Description
- Allow new users to register from the frontend
- Default Value
- true
- Key
- registrationForm
- Type
- string
- Description
- The form to use when registering a new user
- Default Value
- \FwsDoctrineAuth\Form\RegisterForm::class
- Key
- registrationCallback
- Type
- string
- Description
- Callback class to add custom login code
- Default Value
- Key
- autoRegistrationLogin
- Type
- boolean
- Description
- Allow user to be automatically logged-in after registration
- Default Value
- false
- Key
- userActiveAfterRegistration
- Type
- integer
- Description
- User active after registration (1 = active, 0 = inactive). Inactive users can not login
- Default Value
- 1
Login
- Key
- loginForm
- Type
- string
- Description
- The form to use when logging in a user
- Default Value
- \FwsDoctrineAuth\Form\LoginForm::class
- Key
- loginCallback
- Type
- string
- Description
- Callback class to add custom registration code
- Default Value
- Key
- maxLoginAttempts
- Type
- integer
- Description
- Maximum number of allowed login attempts before blocking IP address
- Default Value
- 5
- Key
- loginReleaseTime
- Type
- integer
- Description
- Number of minutes user remains blocked for, 0 = don't unblock. If used should be greater than the above maxLoginAttemptsTime
- Default Value
- 0
Password reset
- Key
- allowPasswordReset
- Type
- boolean
- Description
- Show reset password link on login form and allow password reset (true or false)
- Default Value
- true
- Key
- emailResetLinkForm
- Type
- string
- Description
- The form to use when resetting password
- Default Value
- \FwsDoctrineAuth\Form\EmailForm::class
- 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
- 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
Form labels
- 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
Two factor authentication
- Key
- useTwoFactorAuthentication
- Type
- boolean
- Description
- Use two factor authentication
- Default Value
- false
- Key
- selectTwoFactorAuthMethodForm
- Type
- string
- Description
- Select authentication method during login form, appears after user enters password successfully
- Default Value
- \FwsDoctrineAuth\Form\SelectTwoFactorAuthMethodForm::class
- Key
- twoFactorAuthCodeForm
- Type
- string
- Description
- Enter authentication code form, appears after user selects authentication method from above form
- Default Value
- \FwsDoctrineAuth\Form\TwoFactorAuthCodeForm::class
- Key
- allowedTwoFactorAuthenticationMethods
- Type
- array
- Description
- Array of 2FA methods to use, \FwsDoctrineAuth\Model\TwoFactorAuthModel::EMAIL, \FwsDoctrineAuth\Model\TwoFactorAuthModel::SMS and/or \FwsDoctrineAuth\Model\TwoFactorAuthModel::GOOGLEAUTHENTICATOR
- Default Value
- [\FwsDoctrineAuth\Model\TwoFactorAuthModel::EMAIL]
- Key
- twoFactorCodeActiveFor
- Type
- integer
- Description
- 2FA auth code active time in minuets after generation, user must enter code before time expires. Email and SMS auth only
- Default Value
- 10
- Key
- bulkSmsUsername
- Type
- string
- Description
- Your BulkSMS username, required for SMS codes
- Default Value
- yourUsername
- Key
- bulkSmsApiTokenId
- Type
- string
- Description
- Your BulkSMS API token id
- Default Value
- yourApiToken
- Key
- bulkSmsApiTokenSecret
- Type
- string
- Description
- Your BulkSMS API token secret
- Default Value
- yourApiTokenSecret
-
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, resources (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 accessible to guest users. Examples of user roles are, user, author and admin. User roles can also inherit 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 inherit 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 resource 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 inherit 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 resource 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 qualified class 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 qualified class 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 earlier 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 earlier
- 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 accessible 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 accessible 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 FwsDoctrineAuth\Entity\PasswordReminder;
use FwsDoctrineAuth\Entity\TwoFactorAuthMethods;
use FwsDoctrineAuth\Entity\LoginLog;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
use FwsDoctrineAuth\Model\TwoFactorAuthModel;
use DateTimeInterface;
use DateTimeImmutable;
/**
* 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 int|null
*
* @ORM\Column(name="user_id", type="integer", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private ?int $userId = null;
/**
* identity_property in config
* @var string
*
* @ORM\Column(name="email_address", type="text", nullable=false)
*/
private string $emailAddress = '';
/**
* credential_property in config
* @var string
*
* @ORM\Column(name="password", type="string", length=256, nullable=true)
*/
private ?string $password = null;
/**
*
* @var string|null
*
* @ORM\Column(name="mobile_number", type="text", nullable=true)
*/
private ?string $mobileNumber = null;
/**
* @var bool
*
* @ORM\Column(name="user_active", type="boolean", nullable=false, options={"default":0})
*/
private bool $userActive = false;
/**
* @var bool
*
* @ORM\Column(name="encrypted", type="boolean", nullable=false, options={"default":0})
*/
private bool $encrypted = false;
/**
* @var DateTimeInterface
*
* @ORM\Column(name="date_created", type="datetime")
*/
private DateTimeInterface $dateCreated;
/**
* @var DateTimeInterface
*
* @ORM\Column(name="date_modified", type="datetime")
*/
private DateTimeInterface $dateModified;
/**
* @var UserRoles
*
* @ORM\ManyToOne(targetEntity="FwsDoctrineAuth\Entity\UserRoles", cascade={"persist", "merge"}, fetch="EAGER")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="user_role_id", referencedColumnName="user_role_id")
* })
*/
private UserRoles $userRole;
/**
* @var PasswordReminder|null
*
* @ORM\OneToOne(targetEntity="FwsDoctrineAuth\Entity\PasswordReminder", mappedBy="user", orphanRemoval=true, cascade={"persist", "merge"})
*/
private ?PasswordReminder $passwordReminder = null;
/**
* @var Collection
*
* @ORM\OneToMany(targetEntity="FwsDoctrineAuth\Entity\TwoFactorAuthMethods", mappedBy="user", orphanRemoval=true, cascade={"persist", "merge"}, fetch="EAGER")
*/
private Collection $authMethods;
/**
* @var Collection
*
* @ORM\OneToMany(targetEntity="FwsDoctrineAuth\Entity\LoginLog", mappedBy="user", orphanRemoval=true, cascade={"persist", "merge"}, fetch="EAGER")
*/
private Collection $logins;
/* 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|null
*
* @ORM\Column(name="company_name", type="text", nullable=true)
*/
private ?string $companyName = null;
public function getCompanyName(): ?string
{
return $this->companyName;
}
public function setCompanyName(?string $companyName): Users
{
$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 entities 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 --complete
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
Using two factor authentication Return to top
As of version 0.3.0 Doctrine Auth now offers two factor authentication. This is designed as an addition to the standard email/username and password method and not as a replacement. As standard when a user logs in they enter their credentials as normal and are then moved on to 2FA if the correct credentials are entered. If they have more than one method set they will be asked which 2FA method they wish to use, once selected they are asked to enter the 2FA code generated by the selected method. Once validated the user is redirected as usual. There is also a select 2FA method action which an authenticated in user can select their preferred 2FA method(s). This can be found at www.yoursite.com/auth/select-two-factor-authentication. 2FA forms and views can be customised as any other form/view in Doctrine Auth.
2FA is disabled by default, to enable 2FA you need to set the config key useTwoFactorAuthentication to true. There are several settings required by 2FA, please see the config section for more info on configuration settings.
Encrypting your database Return to top
One thing missing from Doctrine Auth was the encryption of user data. This is important in the case of your database being leaked, having fields encrypted protects sensitive user data. As has been mentioned in the introduction, if you are using the email address as your identity property then unfortunately this cannot be encrypted due to the way Laminas authentication works. This is not an issue if you are using a different identity property such as username.
Creating your RSA encryption/decryption keys
In order to use encryption you need to create an RSA key pair with openssl, please ensure that this is installed. To generate your keys start with the following command.

$ openssl genrsa -out key.pem 1024
This creates your private key, "key.pem" file. Also you will be asked for a passphrase, please enter this in the config. Please note a passphrase is optional but recommended. To extract your public key and create your "key.pub" file simply use.

$ openssl rsa -in key.pem -pubout > key.pub
Don’t forget to copy your keys to the folder you specified in the config.
Enabling encryption and setting your Doctrine entities
Encryption is disabled by default, to enable it you must set the encryptData config key to true. Also if you have an existing user database you may need to encrypt the data in the users table, please see below for the command line commands to do this. If you have not entered any user mobile numbers then there is no need to encrypt, please check your database with your preferred database tool. Also any properties in your user table that you wish to encrypt need to be set to type text in your entity.

<?php
namespace YourNamespace\Entity;
use FwsDoctrineAuth\Entity\BaseUsers;
use Doctrine\ORM\Mapping as ORM;
/**
* Users
* @ORM\Entity
*/
class Users extends BaseUsers
{
/**
* @var string|null
*
* @ORM\Column(name="company_name", type="text", nullable=true)
*/
private ?string $companyName = null;
}
Encrypt/decrypt existing data
Ensure you backup your database first.
To encrypt your database user data you will ideally need to use my Doctrine module command line encryption scripts. These simplify the encryption and decryption of data. To encrypt the users data you will need to use the following.
Perform dry run, do not save entities to database.

$ vendor/bin/doctrine-module doctrine-auth:encrypt-users -v --dry-run
Live run, save entities to database.
use this with caution

$ vendor/bin/doctrine-module doctrine-auth:encrypt-users -v
To decrypt data simply use.

$ vendor/bin/doctrine-module doctrine-auth:decrypt-users -v
There are three options you can use with all encryption/decryption scripts. --dry-run attempts to process the entities without saving them back to the database. -v is verbose mode which will output more information. --help will display the scripts options.
After the options you can specify an optional list of entity properties to encode/decode.
$ vendor/bin/doctrine-module doctrine-auth:encrypt-users -v customProperty1 customProperty2 ... customPropertyX
$ vendor/bin/doctrine-module doctrine-auth:decrypt-users -v customProperty1 customProperty2 ... customPropertyX
As a bonus I have also included encryption/decryption commands for other Doctrine entities too. Obviously you must encrypt/decrypt these property values manually in your app using the trait and/or model below or by using the decrypt view helper.
To encrypt other entities simply use the commands below. You can also use the –dry-run option as above to test before committing the changes to the database.
$ vendor/bin/doctrine-module doctrine-auth:encrypt-entity -v EntityName property1 property2 ... propertyX
$ vendor/bin/doctrine-module doctrine-auth:decrypt-entity -v EntityName property1 property2 ... propertyX
You don’t need to use the fully qualified class name of the entity as the command will search for it.
Encryption/decryption in your app
Your entities custom properties will need to be encrypted/decrypted manually in your app using my crypt class below. If any of these properties are used in the registration form you can use the register method custom code to encrypt these properties.

<?php
namespace YourNamespace\Model;
use YourNamespace\Entity\Users;
use Laminas\Form\FormInterface;
use Laminas\Stdlib\Parameters;
use FwsDoctrineAuth\Model\EncryptTrait;
use FwsDoctrineAuth\Model\Crypt;
/**
* Registration
*
* @author Garry Childs <info@freedomwebservices.net>
*/
class Registration
{
use EncryptTrait;
/**
* Custom registration code
* @param Users $userEntity The user entity, extends BaseUsers
* @param FormInterface $form The registration form
* @param Parameters $postData Posted parameters from request object
* @param array $config Laminas configuration
* @return void
*/
public function __invoke(Users $userEntity, FormInterface $form, Parameters $postData, array $config): void
{
/* Set crypt model */
$this->setCrypt(new Crypt($config));
/* User entity not encrypted */
if ($userEntity->isEncrypted() === false) {
return;
}
/* Encrypt property companyName */
$this->entity = $this->encryptFields($this->entity, ['companyName']);
}
}
The above example uses the FwsDoctrineAuth\Model\EncryptTrait to do the encryption by passing the FwsDoctrineAuth\Model\Crypt model. EncryptTrait has the following methods.
Method | Visibility | Description | Returns |
---|---|---|---|
setCrypt(\FwsDoctrineAuth\Model\Crypt $crypt) | public | Sets the crypt model | self |
getCrypt() | public | Returns the crypt model | \FwsDoctrineAuth\Model\Crypt |
encryptFields(\FwsDoctrineAuth\Entity\EntityInterface $entity, array $properties) | public | Encrypts the specified entity properties | \FwsDoctrineAuth\Entity\EntityInterface |
decryptFields(\FwsDoctrineAuth\Entity\EntityInterface $entity, array $properties) | public | Decrypts the specified entity properties | \FwsDoctrineAuth\Entity\EntityInterface |
- Method
- setCrypt(\FwsDoctrineAuth\Model\Crypt $crypt)
- Visibility
- public
- Description
- Sets the crypt model
- Returns
- self
- Method
- GetCrypt()
- Visibility
- public
- Description
- Returns the crypt model
- Returns
- \FwsDoctrineAuth\Model\Crypt
- Method
- encryptFields(\FwsDoctrineAuth\Entity\EntityInterface $entity, array $properties)
- Visibility
- public
- Description
- Encrypts the user entity properties plus any additional properties specified
- Returns
- \FwsDoctrineAuth\Entity\EntityInterface
- Method
- decryptFields(\FwsDoctrineAuth\Entity\EntityInterface $entity, array $properties)
- Visibility
- public
- Description
- Decrypts the user entity properties plus any additional properties specified
- Returns
- \FwsDoctrineAuth\Entity\EntityInterface
The crypt model mentioned above has a few handy encryption/decryption methods. To initialize the class simply use.

<?php
use FwsDoctrineAuth\Model\Crypt;
// ...
$crypt = new Crypt($config);
Where $config is the Laminas configuration array, should be injected in to your class from a factory.
The crypt model has the following methods.
Method | Visibility | Description | Returns |
---|---|---|---|
setConfig(array $config) | public | Sets the Laminas config, not needed if config is passed into constructor as above. | \FwsDoctrineAuth\Model\Crypt |
rsaEncrypt(string $value) | public | Encrypt value using RSA encryption. | string or null on failure |
rsaDecrypt(string $value) | public | Decrypt value using RSA decryption. | string or null on failure |
bcrypytCreate(string $password) | public | Encrypt password using bcrypt one way encryption. | string |
bcryptVerify(string $password, string $encryptedPassword) | public | Verify bcrypt encrypted password. | boolean |
- Method
- setConfig(array $config)
- Visibility
- public
- Description
- Sets the Laminas config, not needed if config is passed into constructor as above.
- Returns
- \FwsDoctrineAuth\Model\Crypt
- Method
- rsaEncrypt(string $value)
- Visibility
- public
- Description
- Encrypt value using RSA encryption.
- Returns
- string or null on failure
- Method
- rsaDecrypt(string $value)
- Visibility
- public
- Description
- Decrypt value using RSA decryption.
- Returns
- string or null on failure
- Method
- bcrypytCreate(string $password)
- Visibility
- public
- Description
- Encrypt password using bcrypt one way encryption.
- Returns
- string
- Method
- bcryptVerify(string $password, string $encryptedPassword)
- Visibility
- public
- Description
- Verify bcrypt encrypted password.
- Returns
- boolean
Doctrine Auth also ships with a decrypt view helper, this makes it possible to pass RSA encrypted data straight from your entity into a view script.
In your view scripts simply use

<?= $this->decrypt($encryptedValue); ?>
and in your view helpers use

<?= $this->view->decrypt($encryptedValue); ?>
Extending the standard forms Return to top
The registration form
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 YourNamespace\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,
],
],
];
Login and other forms
The login form can be extended in a similar fashion if required. Also the reset email, reset password, select 2FA method and 2FA code forms 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_folder/register.phtml', // override register form view
'fws-doctrine-auth/index/login' => __DIR__ . '/../view/your_folder/login.phtml', // override login form view
'fws-doctrine-auth/index/password-reset' => __DIR__ . '/../view/your_folder/password-reset.phtml', // override password reset view
'fws-doctrine-auth/emails/password-reset' => __DIR__ . '/../view/your_emails_folder/password-reset.phtml', // override password reset email
'fws-doctrine-auth/index/select-two-factor-authentication' => __DIR__ . '/../view/your_folder/select-two-factor-authentication.phtml', // override select which 2FA method(s) to use
'fws-doctrine-auth/index/set-google-authentication' => __DIR__ . '/../view/your_folder/set-google-authentication.phtml', // override set Google Authentication app method (displays QR code)
'fws-doctrine-auth/index/select-auth-method' => __DIR__ . '/../view/your_folder/select-auth-method.phtml', // override select 2FA method view (appears after login)
'fws-doctrine-auth/index/authenticate' => __DIR__ . '/../view/your_folder/authenticate.phtml', // override enter 2FA code view
'fws-doctrine-auth/emails/email-code' => __DIR__ . '/../view/your_emails_folder/email-code.phtml', // override email 2FA code email
'fws-doctrine-auth/sms/text-code' => __DIR__ . '/../view/your_sms_folder/text-code.phtml', // override SMS 2FA code text (please keep this as short as possible)
),
),
Refer to each built-in view script to see how the view is rendered.
Here is an example of a custom register form.
<h2><?= $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><?= $this->formLabel($this->form->get('companyName')); ?></dt>
<dd><?= $this->formText($this->form->get('companyName')) . $this->formElementErrors($this->form->get('companyName')); ?></dd>
<dt><?= $this->formLabel($this->form->get('emailAddress')); ?></dt>
<dd><?= $this->formText($this->form->get('emailAddress')) . $this->formElementErrors($this->form->get('emailAddress')); ?></dd>
<dt><?= $this->formLabel($this->form->get('password')); ?></dt>
<dd><?= $this->formPassword($this->form->get('password')) . $this->formElementErrors($this->form->get('password')); ?></dd>
<dd><?= $this->formHidden($this->form->get('csrf')) . $this->formElementErrors($this->form->get('csrf')); ?>
<?= $this->formSubmit($this->form->get('submit')); ?></dd>
</dl>
<?= $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 invokable class that contains this code. An example can be seen below.

<?php
namespace YourNamespace\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 invokable class that contains this code. An example can be seen below.

<?php
namespace YourNamespace\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 integration Return to top
Doctrine Auth comes with laminas-navigation integration 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 multiple 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 environments, please backup your databases before doing the update command.
To check if the doctrine entities and database schema are in sync 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