Build an Authentication module with Zend 3

In this article, we will build a simple authentication module with a login form and a small administrative interface. Basically we will work on the userdemo module detailed on GitHub at this url :

User Demo

Starting from this code we will add a few tweaks to go further with Zend integration, so if you are new to Zend 3 then you can follow this current page and go back and forth while implementing the code from GitHub. We also advice to copy/paste the lines and not download the whole project for a better understanding while going through the project.

Checkout the end result :

Zend authentication module

Zend - manage users

See the Logs in the table, this is an additional entity within the userdemo module.

Summary of the authentication module

To get a glance at what is being covered here, take a note at the keywords here :

  • Zend Application & module creation
  • Use of Eclipse GUI
  • Zend schema
  • Doctrine
  • Doctrine migration
  • Session management
  • Authentication & access to admin interface
  • Doctrine onetomany relation mapping

As the userdemo project on GitHub, we will implement Doctrine to manage the database tables entries and Zend 3.0 as the Php 7 platform.

I assume you already followed a tutorial from Zend on setting up the MVC framework, so I will quickly lead you to the point. Make sure you are in development mode though :

$ composer development-status

If you haven’t installed Zend along with Php, then I suggest you to read this article on Zend.

Eclipse GUI

I like to use Eclipse along with composer and phpunit. You should get use to it, While Notepad++ is a great tool, when it comes to deal with large structures of classes and using a framework, an IDE interface is necessary.

Eclipse is free and is a great tool, you will be able to use it on Java projects (Android) and windows projects (C++)…

Eclipse GUI

Zend Module Creation

Generate a new application :

Using composer, type this into a folder outside the eclipse workspace :

$ composer create-project –stability=”dev” zendframework/skeleton-application MyProject

The above command will generate the skeleton files we need to get started with our new application, the module comes after and is loaded via the main application. If you already have an application and wish to use it then skip the step.

NOTE : the name of the module here is MyAuth, but you can still use the userdemo name or another one.

Generate a new module inside our application :

Now on Eclipse refresh that folder within your current project once you created the folder : module/MyAuth

REMEMBER : Always respect the standards, for instance here the module name starts with a capital letter : M

Your module directory should sit next to Application

Default folders and files

Let’s continue and build the following files’ structure :

 
MyProject
 /module 
    /MyAuth 
       /config 
       /src 
         /Controller 
         /Form 
         /Model 
       /view 
         /my-auth 
           /index 

This is the default structure for a new application, check the userdemo for additional folders.

We now need to create the class called by the module manager :

module/MyAuth/src/Module.php

 namespace MyAuth;

use Zend\ModuleManager\Feature\ConfigProviderInterface;

class Module implements ConfigProviderInterface {

public function getConfig() {

return include __DIR__ . '/../config/module.config.php';

}

}

Now let’s follow Zend recommandations and tell composer to autoload our new module :

"autoload" : {
"psr-4" : {
"Application\\" : "module/Application/src/",
"MyAuth" : "module/MyAuth"
}
},

From Eclipse, take the source code of the json file to make changes.
And run the command to update the rule :

$ composer dump-autoload

Still with the autoloading feature, we need to create a config file and for the full config file, refer to GitHub userdemo module.

The config file is called by the getConfig() method we saw earlier.
As you may notice, the configuration components which are passed through the service manager (InvokableFactory) are referring to the Controller and View of the module.

Before we dive into the code, let me show you a simple schema of Zend :

It needs improvements and will be updated later, don’t hesitate to post a comment.

Now let’s get ready to implement the userdemo code by installing a few components :

First of all, make sure you have these components installed :

zend-authentication

zend-session

Your modules.config.php file should now look like this :


return [
 'Zend\Router',
 'Zend\Validator',
'Zend\Session',
// Add the Doctrine integration modules.
'DoctrineModule',
'DoctrineORMModule', 
'Application',
'MyAuth',
];

But for the whole code to work we need a few more plugins :


return [
'Zend\Router',
'Zend\Validator',
'Zend\Session',
'Zend\Cache',
'Zend\Paginator',
'Zend\I18n',
'Zend\InputFilter',
'Zend\Filter',
'Zend\Hydrator',
'Zend\Mvc\Plugin\Prg',
'Zend\Mvc\Plugin\Identity',
'Zend\Mvc\Plugin\FlashMessenger',
'Zend\Mvc\Plugin\FilePrg',
'Zend\Form',
// Doctrine integration modules.
'DoctrineModule',
'DoctrineORMModule',
'Application',
'MyAuth',
];

Reminder :

$ composer require zendframework/zend-i18n

$ composer require zendframework/zend-mvc-plugin-flashmessenger

$ composer require zendframework/zend-mvc-plugin-fileprg

$ composer require zendframework/zend-mvc-plugin-prg

$ composer require zendframework/zend-mvc-plugin-identity

$ composer require zendframework/zend-crypt

$ composer require zendframework/zend-captcha

For Captcha, we need to enable the GD module of PHp :

$ sudo apt-get install php5-gd

 

The Database

I like to start with the database scheme for my projects so I can get a clear view on what I plan to do. Besides the Mysql schema, I’m used to draw diagram(s) with relationships between modules.

Following is the table being used in this article :


CREATE TABLE `user` (
`id` INT NOT NULL AUTO_INCREMENT ,
`user_email` VARCHAR(32) NOT NULL ,
`user_password` VARCHAR(256) NOT NULL ,
user_name VARCHAR(32) NOT NULL,
pwd_reset_token VARCHAR(32) NULL,
pwd_reset_token_creation_date DATETIME NULL,</pre><pre>date_created DATETIME NOT NULL,
PRIMARY KEY (`id`),
UNIQUE(user_email)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

 Note that the table name will be the same as the user entity.

 Now the logs about each user sign-in success (this part isn’t covered inside the userdemo module) :

CREATE TABLE `log` (
`id` INT NOT NULL AUTO_INCREMENT ,
`user_id` INT NOT NULL ,
date_log DATETIME NOT NULL,
PRIMARY KEY (`id`),
UNIQUE(user_id,date_log),
CONSTRAINT FOREIGN KEY (`user_id`) 
REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

See the relationship here ? the user_id field inside the log table refers to the user id from the user table and because one user can have infinite logs, then this is a onetomany relationship. We will see this later while dealing with Doctrine.

I enjoy Mysql and the code above speaks to me so I keep using it and when you use the Mysql Database design tool, it is really useful, however for the current project I chose not to use the Mysql code but I coded the tables right inside the project inside a php file using Doctrine Migrations class.

You could also use Yaml or XML these data serialization standards are really powerful, we will review them in other tutorials.

 

The module

To build a simple login module, there is no much concern about the design, the main page will contain the login form which will be triggered whenever a user is not signed in.

Following are the scenarios for the main page :

  • New user – login form
  • Existing user but not signed in – login form
  • Signed in user – on the userdemo the user accesses the list of users…
  • Signing out user – login page or home page with sign in button.

If you think about the content of the sign in page, check the list :

  • login form
  • forgot password link
  • registration link (covered inside another tutorial)

Routes & Controllers

Remember : Zend framework expects the pages to be organized and a page is seen as an action which are grouped into controllers within modules.

Our simple module will use only one controller with the following actions :

Page Controller Action
Home AuthController index
Add new user AuthController add
Edit user profile AuthController edit

There are additional actions, refer to the userdemo module on Github

Besides userdemo default actions, we will add a new action inside IndexController.php :

<br data-mce-bogus="1">
    /** 
     * This action displays the user logs
     */
    
    public function logsAction() 
    {
        $id = (int)$this->params()->fromRoute('id', -1);
        
        if ($id<1) {
            $this->getResponse()->setStatusCode(404);
            return;
        }
        
        $user = $this->entityManager->getRepository(User::class)
        ->findOneBy(array('id' => $id));
        
        
        if ($user == null) {
            $this->getResponse()->setStatusCode(404);
            return;
        }
        
        
        return new ViewModel([
                'logs' => $user->getLogs(),
                'id'   => $user->getId()
        ]);
        
    }

Working with the Model View Controller schema leads us to the routes to map an action from its url :

  • this is achieved within the module.config.php file :
'myauth' => [
        'type'    => Segment::class,
        'options' => [
                'route'    => '/myauth[/:action[/:id]]',
                'constraints' => [
                        'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
                        'id' => '[a-zA-Z0-9_-]*',
                ],
                'defaults' => [
                        'controller'    => Controller\IndexController::class,
                        'action'        => 'index',
                ],
        ],
],

To add the log action to our route, we don’t need to update the module.config.php file as it already contains everything we need :

  ‘route’    => ‘/myauth[/:action[/:id]]’

So we are done with the controller part.

 

The Model

The model section is the core of your application and here with the authentication module, it will make the right calls to the database.

For this project, we will be using Doctrine which is an Object Relational Mapping (ORM) tool, but you may choose to use the usual entity relational method.

Doctrine prerequisites :

We need both following modules :

  • DoctrineModule
  • DoctrineORMModule

Load them from your application if you haven’t done it yet :

$ composer require doctrine/doctrine-orm-module

This command will update the composer.json file accordingly with the following line :

doctrine/doctrine-orm-module”: “^1.1”

Enable the Doctrine module with the modules.config.php file within your project config folder (you should already have the Doctrine lines in it if you have read the corresponding part at the beginning of this page).

For the sake of this project we decide to create an application-wide database connection with a Mysql database. The configuration will happen inside the autoload/global.php file :

use Zend\Session\Storage\SessionArrayStorage;
use Zend\Session\Validator\RemoteAddr;
use Zend\Session\Validator\HttpUserAgent;
use Doctrine\DBAL\Driver\PDOMySql\Driver as PDOMySqlDriver;


return [
        // Session configuration.
        'session_config' => [
                'cookie_lifetime'     => 60*60*1, // Session cookie will expire in 1 hour.
                'gc_maxlifetime'      => 60*60*24*30, // How long to store session data on server (for 1 month).
        ],
        // Session manager configuration.
        'session_manager' => [
                // Session validators (used for security).
                'validators' => [
                        RemoteAddr::class,
                        HttpUserAgent::class,
                ]
        ],
        // Session storage configuration.
        'session_storage' => [
                'type' => SessionArrayStorage::class
        ],
        
        
        'doctrine' => [
                // migrations configuration
                'migrations_configuration' => [
                        'orm_default' => [
                                'directory' => 'data/Migrations',
                                'name'      => 'Doctrine Database Migrations',
                                'namespace' => 'Migrations',
                                'table'     => 'migrations',
                        ],
                ],
                'connection' => [
                        'orm_default' => [
                                'driverClass' => PDOMySqlDriver::class,
                                'params' => [
                                        'host'     => '127.0.0.1',
                                        'user'     => 'test',
                                        'password' => 'debian',
                                        'dbname'   => 'test',
                                ]
                        ],
                ], 
        ],
        // ...
];

In the mean time, make sure you create the corresponding database and provide access to the user.

With Doctrine you can add more database connections if needed. You can refer to the documentation of Doctrine and Zend 3.

AS you may have noticed we added the migrations configuration data in the file which makes it different from the userdemo project.

Doctrine User entity

A user entity is a Php class designed to store user data such as user name password… With Doctrine, the entity class is mapped on a database table, here it will map on the user table.

Please refer to GitHub for the source code but add/update the following lines to add the relationship with the log table :

    /**
     * @ORM\OneToMany(targetEntity="\MyAuth\Entity\Log", mappedBy="user_id", cascade={"persist", "remove"})
     */
    protected $logs;

We retrieve here the OneToMany relation between the log table and user table. Here the mappedBy data refers to the protected variable inside the log entity. Check the Doctrine documentation for more details :

Doctrine Association Mapping

    /**
     * Constructor.
     */
    public function __construct()
    {
        $this->logs = new ArrayCollection();
    }
    
    /**
     * Returns logs for this user.
     * @return array
     */
    public function getLogs()
    {
        return $this->logs;
    }
    
    /**
     * Adds a new log to this user.
     * @param $log
     */
    public function addLog()
    {
        // Log is an array with user_id and dateLog
        $newlog = new Log();
                
        $currentDate = date('Y-m-d H:i:s');
        
        $newlog->setDate($currentDate);
        $newlog->setUserId($this->id);
        
        $this->logs->add($newlog);
    }

You must also use the following class in order to use the Arraycollection provided by Doctrine :

use Doctrine\Common\Collections\ArrayCollection;

If you refer to the Doctrine documentation, you will see the annotations /* ORM */. These annotations are mandatory.

Here comes the user logs part :

 namespace MyAuth\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* This class represents a log.
* @ORM\Entity
* @ORM\Table(name="log")
*/
class Log
{

/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(name="id")
*/
protected $id;

/**
* @ORM\Column(name="user_id")
* @ORM\ManyToOne(targetEntity="\MyAuth\Entity\User", inversedBy="logs")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user_id;

/**
* @ORM\Column(name="date_log")
*/
protected $date_log;

// Returns ID of this log.
public function getId()
{
return $this->id;
}

// Sets ID of this log.
public function setId($id)
{
$this->id = $id;
}
// Returns User ID of this log.
public function getUserId()
{
return $this->user_id;
}

// Sets ID of this log.
public function setUserId($id)
{
$this->user_id = $id;
}

// Returns date.
public function getDate()
{
return $this->date_log;
}

// Sets name.
public function setDate($date_log)
{
$this->date_log = $date_log;
}
} 

We see here simple getters and setters and also the inverseBy mapping.

Each time a user is logging in, it will be written in the table.

 

More with Doctrine

If you plan on using Doctrine’ s commands, you need to add some configuration inside your project, the following are provided by Doctrine’s documentation :

<?php
// cli-config.php
require_once "bootstrap.php";

return \Doctrine\ORM\Tools\Console\ConsoleRunner::createHelperSet($entityManager);

And the bootstrap code :

<?php
// bootstrap.php
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;

require_once "vendor/autoload.php";

// Create a simple "default" Doctrine ORM configuration for Annotations
$isDevMode = true;
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode);
// or if you prefer yaml or XML
//$config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
//$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);

// database configuration parameters
$conn = array(
        'driver' => 'pdo_sqlite',
        'path' => __DIR__ . '/db.sqlite',
);

// obtaining the entity manager
$entityManager = EntityManager::create($conn, $config);

The above file should be created so we can use the tools:
$ doctrine orm:validate-schema

That command will help us debugging.
And also :

You can easily recreate the database:

$ vendor/bin/doctrine orm:schema-tool:drop --force
$ vendor/bin/doctrine orm:schema-tool:create

Or use the update functionality:

$ vendor/bin/doctrine orm:schema-tool:update --force
However, we won't use the above commands to build this module but the migrations' component. 

If you plan on adding modules, updating the database while you develop, doctrine migrations’ component is the way to go, install the following package :


$ composer require doctrine/migrations

This command will automatically add a new entry inside your composer.json file :


"require" : {
"php" : "^5.6 || ^7.0",
"zendframework/zend-component-installer" : "^1.0 || ^0.7 || ^1.0.0-dev@dev",
"zendframework/zend-mvc" : "^3.0.1",
"zfcampus/zf-development-mode" : "^3.0",
"doctrine/doctrine-orm-module": "^1.1",
"doctrine/migrations": "^1.5"
},

Next, make sure you added the migration configuration lines inside your global autoload file (go up a little and you’ll see the code !)
You can now create that directory :

/YourProject/data/Migrations

To generate a new empty migration, type the command inside your project directory :

$ ./vendor/bin/doctrine-module migrations:generate

Following is the result of this command :

Loading configuration from the integration code of your framework (setter).
Generated new migration class to “data/Migrations/Version20170512123739.php”

Check the Version…php file to see the structure !

All we have to do now it to fill this file with our database schema :

<?php

namespace Migrations;

use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;

/**
 * Auto-generated Migration: Please modify to your needs!
 */
class Version20170517091956 extends AbstractMigration
{
    /**
     * Returns the description of this migration.
     */
    public function getDescription()
    {
        $description = 'This is the initial migration which creates users tables.';
        return $description;
    }
    
    
    /**
     * @param Schema $schema
     */
    public function up(Schema $schema)
    {
        // this up() migration is auto-generated, please modify it to your needs
        
        // Create 'projects_users' table
        $table = $schema->createTable('user');
        $table->addColumn('id', 'integer', ['autoincrement'=>true]);
        $table->addColumn('user_email', "string", array("length" => 32,'notnull' => true));
        $table->addColumn('user_password', "string", array("length" => 256 , 'notnull' => true));
        $table->addColumn('user_name', "string", array("length" => 32 , 'notnull' => true));
        $table->addColumn('pwd_reset_token', "string", array("length" => 32 , 'notnull' => false));
        $table->addColumn('pwd_reset_token_creation_date', 'datetime', ['notnull'=>false]);
                
        $table->addColumn('date_created', 'datetime', ['notnull'=>true]);
        $table->setPrimaryKey(['id']);
        $table->addUniqueIndex(['user_email'],'user_email_index',[]);
        $table->addOption('engine' , 'InnoDB');
        
        // Create 'logs' table
        $table = $schema->createTable('log');
        $table->addColumn('id', 'integer',['autoincrement'=>true]);
        $table->addColumn('user_id', 'integer', ['notnull'=>true]);
        $table->addColumn('date_log', 'datetime', ['notnull'=>true]);
        $table->setPrimaryKey(['id']);
        $table->addUniqueIndex(['user_id','date_log'],'user_date_index',[]);
        $table->addForeignKeyConstraint('user', ['user_id'], ['id'], [], 'user_id_fk');
        $table->addOption('engine' , 'InnoDB');
    }
    
    /**
     * @param Schema $schema
     */
    public function down(Schema $schema)
    {
        // this down() migration is auto-generated, please modify it to your needs
        $table = $schema->getTable('user');
        $table->dropIndex('user_email_index');
        
        $schema->dropTable('log');
        $schema->dropTable('user');
        
    }
}

When you analyze the above code, you will see that we handle the table creation along with primary keys, index keys and foreign keys. We also take care of the cleaning in case of an update inside the down method.

Now execute :

./vendor/bin/doctrine-module migrations:migrate

Doctrine database migrations

And appreciate the result inside your database :

doctrine migrations table

If you look inside your migrations table you will see the version number of your file.

 

The Session

To get the session manager working smoothly, make sure you have the right code inside your global.php file and refer to the userdemo source code and documentation on GitHub. I strongly suggest you to read the tutorial here :

Working with Sessions

The View

We are getting close to using our module with the entity manager, let’s add a logs.phtml page where the logs will be shown to the logged in user.

Remember the logs action from your indexController when it returns the ViewModel with the array collection (think it as an array) and the user id. The view page (logs.phtml) has just to navigate through the collection of data and display the results as follow :

User logs

Well, this is over for now and I hope you enjoyed the tutorial as much as I did, it is really a pleasure to see how things are being structured using the Zend Platform, if you have followed the external links through your reading, then you should have a working version of the userdemo and are now ready to update it and add some personal touch to it.

In case you are having troubles implementing the whole, then drop a message here, we will be pleased to answer to any questions/suggestions…

Do not forget to follow us on social media to be the first to know whenever there is a new tutorial here at linkstraffic.

More with Zend

The user demo tutorial from GitHub :

https://olegkrivtsov.github.io/using-zend-framework-3-book/html/en/User_Management__Authentication_and_Access_Filtering/Implementing_User_Authentication.html

https://docs.zendframework.com/tutorials/getting-started/skeleton-application/

https://olegkrivtsov.github.io/using-zend-framework-3-book/html/en/Database_Management_with_Doctrine_ORM/Integrating_Doctrine_ORM_with_Zend_Framework_3.html

https://olegkrivtsov.github.io/using-zend-framework-3-book/html/en/Model_View_Controller.html

About Doctrine :

http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html

http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/tools.html#database-schema-generation

 

Leave a Reply

Want more information?

Related links will be displayed here in this section for you to pick up another good spot to get more details about Web marketing and Search Engine Optimization. There will be some sites which we selected to ease the work of any webmaster or/and web marketer on the Internet.

%d bloggers like this: