Welcome Guest

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 Return to top

With Composer Return to top

  1. In the root of your application enter:
    Copy to clipboard $ composer require krytenuk/doctrine-auth

Post installation Return to top

  1. 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',
            
    // ...
        
    ];
  2. Copy `./vendor/krytenuk/doctrine-auth/config/doctrine.auth.config.local.php.dist` to `./config/autoload/doctrine.auth.config.local.php`.
  3. 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 unique
    emailAddress
    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 parents
    redirect 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_DENY
    Set 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 permission
    actions 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 Navigation
    true

    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

Copy to clipboard <?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.

Copy to clipboard <?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

  1. 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`;
  2. and to add the tables using the CLI.
    Copy to clipboard $ vendor/bin/doctrine-module orm:schema-tool:create

Existing database Return to top

Ensure you backup your database first.

  1. On an existing database use.
    Copy to clipboard $ vendor/bin/doctrine-module orm:schema-tool:update --force --complete
    To synchronize your entities with your database tables

For more information on Doctrine CLI see Doctrine Module and Doctrine Console

Once your database tables are setup.

  1. Populate the user roles
    Ensure that you run this command after adding new user roles
    Copy to clipboard $ vendor/bin/doctrine-module doctrine-auth:init
  2. you can also truncate the user roles table prior to populating
    use this with caution
    Copy to clipboard $ 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.

Copy to clipboard $ 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.

Copy to clipboard $ 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.

Copy to clipboard  <?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.

Copy to clipboard $ vendor/bin/doctrine-module doctrine-auth:encrypt-users -v --dry-run

Live run, save entities to database.
use this with caution

Copy to clipboard $ vendor/bin/doctrine-module doctrine-auth:encrypt-users -v

To decrypt data simply use.

Copy to clipboard $ 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.

Copy to clipboard <?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 $userEntityFormInterface $formParameters $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.

Copy to clipboard <?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

Copy to clipboard <?= $this->decrypt($encryptedValue); ?>

and in your view helpers use

Copy to clipboard <?= $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

Copy to clipboard <?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

Copy to clipboard <?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.

Copy to clipboard <?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.

Copy to clipboard <?php

namespace YourNamespace\Model;

use 
FwsDoctrineAuth\Entity\BaseUsers;
use 
Laminas\Form\FormInterface;
use 
Laminas\Stdlib\Parameters;

class 
Registration
{

    public function 
__invoke(BaseUsers $userEntityFormInterface $formParameters $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.

Copy to clipboard <?php

namespace YourNamespace\Model;

use 
FwsDoctrineAuth\Entity\BaseUsers;
use 
Laminas\Form\FormInterface;

class 
Login
{

    public function 
__invoke(BaseUsers $userEntityFormInterface $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

Copy to clipboard <?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

Copy to clipboard <?= $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.

Copy to clipboard <?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.

Copy to clipboard $.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.

Copy to clipboard $ 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

Copy to clipboard $ vendor/bin/doctrine-module orm:validate-schema

And to synchronize your database schema with the entities use

Copy to clipboard $ vendor/bin/doctrine-module orm:schema-tool:update --force
Donate