Frontend

Let's review the work we need to do on the client side of the app. We need to create a new layout in order to remove the header menu, create the signup form and its view, modify the controller to use the new layout and process the sign-up form, modify the controller view to show the sign-up form, and finally adapt ApiClient to be able to send the data to the API side.

The following is the structure of the module:

Frontend

The login form

In the login form, we will ask the user for the username and the password. So, the form will be very simple. Also, when we work with forms, we need to also create the form view and the filter configuration. I'm pretty sure that you are totally capable of creating these files by yourself, so give it a try! Remember that you can always check the source code provided in the book.

Changes in the ApiClient.php file

We need to add a new method named authenticate() in this object in order to be able to authenticate the user. As you can imagine, the method is really simple and we just need to post the data to the API as follows:

protected static $endpointUserLogin = '/api/users/login';

We need to add this line at the top of the file to define the new endpoint, and then add the following method:

public static function authenticate($postData)
{
    $url = self::$endpointHost . self::$endpointUserLogin;
    return self::doRequest($url, $postData, Request::METHOD_POST);
}

Creating a new authentication adapter

Usually, when you use the authentication components from ZF2, you use the DbTable adapter that help you in verifying the credentials against a database, specifying a table name, a username column, and a password column. In our case, as we have taken the API-centric approach, we cannot do this directly from the client, and we need to interface it with the API.

As usual, the ZF2 components provide multiple ways to extend them or modify their behavior to fit our needs, and that's what we are going to do now. Create a new adapter for the authentication component that will use the API we created to validate the credentials. Because this is something totally new, let's review it together line by line.

We will start by creating the Api.php file at the same location as in the preceding screenshot:

namespace CommonAuthenticationAdapter;

use ZendAuthenticationAdapterAdapterInterface;
use ZendAuthenticationResult;
use ApiClientApiClient;
use UsersEntityUser;
use ZendStdlibHydratorClassMethods;

These are the namespaces and components we are going to use in this class. As you can see, we have also included the User entity and the ClassMethods hydrator; you will soon understand why.

The following code defines the class that extends from the base interface for the adapters:

class Api implements AdapterInterface

The following two properties are going to store the credentials a user sends via the form:

private $username = null;
private $password = null;

These properties will be populated in the constructor as given in the following code:

public function __construct($username, $password)
{
    $this->username = $username;
    $this->password = $password;
}

We will create an instance of the adapter manually and specify the username and password at that point for later usage:

public function authenticate()
{
    $result = ApiClient::authenticate(array(
        'username' => $this->username,
        'password' => $this->password
    ));

    if ($result['result'] === true) {
        $hydrator = new ClassMethods();
        $user = $hydrator->hydrate(
            ApiClient::getUser($this->username), new User()
        );

        $response = new Result(
            Result::SUCCESS, 
            $user, 
            array('Authentication successful.')
        );
    } else {
        $response = new Result(
            Result::FAILURE, 
            NULL, 
            array('Invalid credentials.')
        );
    }

    return $response;
}

This is the only method specified on the adapter interface that must be implemented in our class. We have to implement the code, which will check the credentials here and return the result as an instance of ZendAuthenticationResult. In order to create the result object, we need to pass the result/failure of the authentication to the instance at construction time, using the constants provided for the class, identity data, and message.

The identity message is a payload we store on the session to identify the user; you can store the ID of the user or the whole object. In our case, we will store an instance of the User entity so that we can also access the data and related info.

As you can see on the first lines of the method, we use the ApiClient method to check with the API if the credentials are fine. Based on the result from the API, we hydrate an instance of the User entity to store it as an identity data or we generate an error. Finally, no matter what the result is, we generate the specified Result object with the corresponding information in it.

The IndexController.php file

We need to do four changes on this controller. In the indexAction() method, we should check if the user is logged in at the beginning. In that case, we do not need to show the signup form to the user, we will redirect it to the user wall. This is accomplished by adding the following code at the top of the method:

$auth = new AuthenticationService();
$loggedInUser = $auth->getIdentity();

if ($loggedInUser !== null) {
    return $this->redirect()->toRoute(
        'wall', 
        array('username' => $loggedInUser->getUsername())
    );
}

Now, the second change we need to do is at the end of the method. We want to allow the user to log in as soon as his or her account is created. Because we are going to authenticate the user against our API, we need to declare the components we want to use by adding the following code at the top of the file:

use ZendAuthenticationAuthenticationService;
use CommonAuthenticationAdapterApi as AuthAdapter;

We need to add the following code before redirecting the user to the wall to log in:

$auth = new AuthenticationService();
$authAdapter = new AuthAdapter(
    $data['username'], 
    $data['password']
);
$auth->authenticate($authAdapter);

In the preceding code snippet, we created an instance of AuthenticationService, and then created an instance of our adapter aliased here as AuthAdapter. We provided the username and the password to the constructor, and finally, we called the authenticate method on the $auth object, passing the auth adapter.

The third change we have to do in this file is adding the loginAction() method in order to show the login form, and also to process the login. As we are going to create an instance of the login form, we need to import the following code at the top of the file:

use UsersFormsLoginForm;

Now let's review the code we are going to add on the newly created method:

$viewData = array();
$flashMessenger = $this->flashMessenger();

$loginForm = new LoginForm();
$loginForm->setAttribute(
    'action', $this->url()->fromRoute('users-login')
);

The following are the first lines. As you can see, we get an instance of the flash messenger and also create an instance of LoginForm, to set its action:

$request = $this->getRequest();
if ($request->isPost()) {
    ...
}

$viewData['loginForm'] = $loginForm;

return $viewData;

This is the structure of the new block of code. As you can see, we get the request and check if we are processing the form or just showing it. To show it, we assign it to the view and at the end, we also check for any messages available on the flash messenger, and pass them to the view.

The following is the code we will execute when we are processing the form. This code will fit on the suspension points, as we have already seen in the skeleton.

$data = $request->getPost()->toArray();

$loginForm->setInputFilter(User::getLoginInputFilter());
$loginForm->setData($data);

if ($loginForm->isValid()) {
    $data = $loginForm->getData();

    $auth = new AuthenticationService();
    $authAdapter = new AuthAdapter(
        $data['username'], $data['password']
    );
    $result = $auth->authenticate($authAdapter);

    if (!$result->isValid()) {
        foreach ($result->getMessages() as $msg) {
            $flashMessenger->addErrorMessage($msg);
        }
    } else {
        return $this->redirect()->toRoute(
            'wall', array('username' => $data['username'])
        );
    }
}

The code gets the data from the request and configures the filter from the User entity to validate and filter the sent data. If the form is valid, we continue getting the data filtered from the login form, and then we set up the authentication object, as we did in the indexAction() method. In case the login is successful, we redirect the user to the wall; in case of a failure, we will show the error message sent by the API using the flash messenger.

If we allow the users to log in to our product, we also need to provide a way for them to log out and that's the last change we have to do in this controller, as given in the following code:

$auth = new AuthenticationService();
if ($auth->hasIdentity()) {
    $auth->clearIdentity();
}

return $this->redirect()->toRoute('users-login'),

These are the contents of the logoutAction() method. As you can see, we checked if the user has an identity or not. In case he or she has, we just clear the stored identity so that we are not able to identify who the user is. After that, we redirect them to the login page.

The login.phtml file

This is the view that will show the form to the user, it's really simple. As we already saw how to add forms to the view, we will try to define the contents of this file on our own. Remember that you can always check the source code if needed.

Changes in module.config.php

In this chapter, we are adding the ability to log in and log out of our application, and we already completed the methods needed on the controller. The last step is to create the routes so that the methods can be accessed from a browser.

'users-login' => array(
    'type' => 'ZendMvcRouterHttpLiteral',
    'options' => array(
        'route'    => '/login',
        'defaults' => array(
            'controller' => 'UsersControllerIndex',
            'action' => 'login'
        ),
    ),
),
'users-logout' => array(
    'type' => 'ZendMvcRouterHttpLiteral',
    'options' => array(
        'route'    => '/logout',
        'defaults' => array(
            'controller' => 'UsersControllerIndex',
            'action' => 'logout'
        ),
    ),
),

These are the two routes you need to add to point the URLs to an appropriate method in IndexController.

The global.php file

Now that we have two routes available to use, we need to add the links that will actually allow the users to use the log in and log out methods. They can always remember the URLs, but that's not always possible. To add new items on the header menu, we need to modify this file and add the configuration of the new links, as given in the following code:

array(
    'label' => 'Logout',
    'route' => 'users-logout',
    'resource' => 'users-logout'
),
array(
    'label' => 'Login',
    'route' => 'users-login',
    'resource' => 'users-login'
),

If you pay attention to the preceding code, you will notice that there is a new key on the configuration called resource. We will talk more about it later, but for now we can say that the data stored there will control if the item should be shown on the menu or not.

The layout.phtml file

As of now, the users can log in and log out, but we need to show or hide the links on the menu properly. The navigation helper can help us on this task; however, in order to be able to do it, we need to provide some information. We need to replace the following lines of code:

<?php $this->navigation('navigation')->findBy('route', 'wall')
    ->setParams(
        array('username' => $this->loggedInUser->getUsername())
    ); 
?>
<?php $this->navigation('navigation')->findBy('route', 'feeds')
    ->setParams(
        array('username' => $this->loggedInUser->getUsername())
    ); 
?>
<?php echo $this->navigation('navigation')
->menu()->setUlClass('nav')->renderMenu(); ?>

The following code should replace the preceding code:

<?php if ($this->loggedInUser !== null) : ?>
<?php $this->navigation('navigation')->findBy('route', 'wall')
        ->setParams(
            array('username' =>
                $this->loggedInUser->getUsername()
            )
        ); 
    ?>
<?php $this->navigation('navigation')
        ->findBy('route', 'feeds')
        ->setParams(array('username' =>
            $this->loggedInUser->getUsername())
        );
    ?>
<?phpendif; ?>
<?php echo $this->navigation('navigation')
    ->menu()->setUlClass('nav')->setAcl($this->acl)
    ->setRole($this->userRole)->renderMenu(); ?>

As usual, the code of the view is not as neat as we want it to be and is kind of hard to read. But what we are essentially doing here is customizing one of the items of the menu to point to the user wall when the user is logged in, and then providing the navigation helper with the data needed to show or hide the items. In the following sections, we will talk about what that information is, how to create it, and how to work with it.

ACLs

ZF2 provides two ways of handling the permissions of an application. The first one and the one which we use here is the ACLs, the second one is the Rbac. The difference between them is that ACLs is focused on resources, which means short modules, controllers, and actions. The Rbac model focusses more on roles and their permissions, allowing you to create permissions that are not attached to any specific resource. If you need something simple, you can go with Rbac; if you need something more complex, you can go with ACLs. But you usually do almost the same with both the models and they both more or less have the same "glue" code.

In our product, we will use ACLs; the concept behind the Access Control List is allowing roles' request access to resources. A role is an object that needs access somewhere and a resource is an object whose access is protected and controlled. In our case, we will create a role called guest for the users not logged in, and another one called member for the logged-in users. After that, we will create resources to protect the access to all the functionalities we have in place. This sounds a little bit complicated, but I'm sure that you will understand everything after implementing it.

As we are developing everything under specific modules, we are going to provide each module with the ACL configuration that affects that module directly. Then we will load it when ZF2 reads the module.

The ACL implementation on ZF2 is very flexible and allows you to store the configuration of the roles and resources in any way you want. Actually, it's the developer's responsibility to come up with a storage and loading mechanism. We will now see how we are going to do it on our product, but this is surely not the only way to do it and probably not the best one:

<?php

return array(
    'roles' => array(
        'guest',
        'member'
    ),
    'permissions' => array(
        'member' => array(
            'feeds',
            'feeds-subscribe',
            'feeds-unsubscribe'
        )
    )
);

The preceding code is the content of the module.acl.php file stored in the config folder of the Feeds module. As you can see, the configuration consists of an array, inside which we specify the roles that interact with this module and the permissions assigned to each role. In order for a role to access a resource, the resource should be added in here. If you look closely at our resources, you may notice that we are using the names of our routes. In this case, the route is tied to a module, controller, and action and the route will conform to our resource.

In this configuration, we are allowing the members to access the feeds list, the subscription action, and the unsubscribe functionality. The guest users are not allowed to see anything and that's why there is no key for them under the permission key.

The following is the ACL configuration of the Users module and Wall module respectively:

return array(
    'roles' => array(
        'guest',
        'member'
    ),
    'permissions' => array(
        'guest' => array(
            'users-signup',
            'users-login'
        ),
        'member' => array(
            'users-logout'
        )
    )
);

return array(
    'roles' => array(
        'guest',
        'member'
    ),
    'permissions' => array(
        'member' => array(
            'wall'
        )
    )
);

As you can see in the first block, the guest user is allowed to access the login and the signup form, but not the logout functionality. Also, the member is not allowed to go to the login form because he or she is already logged in, and also it is not allowed to go to the signup.

In terms of the Wall module, the only user role allowed is the member.

Modules

Now, let's see how to inform ZF2 about our ACL configuration and how to create them in terms of objects.

On every Module.php file, we need to load the ACL configuration using bootstrap, and then add an event listener to the route event to check if the user has the rights to access the resource.

The objects that conforms the ACL on ZF2 are the Acl class, GenericRole class, and GenericResource class. In order to import them, you should add the following lines of code at the top of each Module.php file that has something to do with ACLs:

use ZendPermissionsAclAcl;
use ZendPermissionsAclRoleGenericRole as Role;
use ZendPermissionsAclResourceGenericResource as Resource;

We need to modify the onBootstrap() method of every module that has ACLs, to load the configuration in every Module.php file. This is accomplished by adding the following line at the top of the method:

$this->initAcl($e);

After that change, we need to implement the initAcl() method as follows:

public function initAcl(MvcEvent $e)
{
    if ($e->getViewModel()->acl == null) {
        $acl = new Acl;
    } else {
        $acl = $e->getViewModel()->acl;
    }

    $aclConfig = include __DIR__ . '/config/module.acl.php';
    $allResources = array();

foreach ($aclConfig['roles'] as $role) {
        if (!$acl->hasRole($role)) {
            $role = new Role($role);
            $acl->addRole($role);
        } else {
            $role = $acl->getRole($role);
        }

        if (array_key_exists(
            $role->getRoleId(), $aclConfig['permissions'])
        ) {
            foreach (
                $aclConfig['permissions'][$role->getRoleId()] as 
                    $resource) {
                if (!$acl->hasResource($resource)) {
                    $acl->addResource(new Resource($resource));
                }
                $acl->allow($role, $resource);
            }
        }
    }

    $e->getViewModel()->acl = $acl;
}

Let's review what are we doing here. In order to share the object that represents the ACL, we need to store it somewhere and be able to access it from the views and the Module.php files. This is why the first line checks if there is something stored in the view model. In case nothing is found, we create an instance of the Acl object.

Right after that, we load the module.acl.php config file to a variable and start iterating over the roles. We then check if each role is already created on the ACL storage. If not, we create it using a new instance of Role. In case it's defined, we just get its instance to use later.

After that, we check if the module has permissions defined; in case we have them, we iterate over them by adding the resources using the instances of Resource and then allow the role stored on the $role variable to access the resource we just created.

This will configure the Acl object with all the roles and permissions of the Wall, Users, and Feeds modules. Also, remember that Role and Resource are just aliases of the classes we are really using.

Finally, after the configuration of the ACLs, we need to check if the user has access to a specific route while loading it. In order to do it, we need to add or register a method with the route event and then insert the code on the Common module.

At the end of the onBootstrap() method, we have to add the following lines of code to register the listener for the event:

$e->getApplication()->getEventManager()->attach(
'route', array($this, 'checkAcl')
);

This will execute the checkAcl() method every time the route event is fired:

public function checkAcl(MvcEvent $e) {
    $route = $e->getRouteMatch()->getMatchedRouteName();
    $routerParams = $e->getRouteMatch()->getParams();
    $auth = new AuthenticationService();

    $userRole = 'guest';
    if ($auth->hasIdentity()) {
        $userRole = 'member';
        $loggedInUser = $auth->getIdentity();
        $e->getViewModel()->loggedInUser = $loggedInUser;
    }

    $e->getViewModel()->userRole = $userRole;

    if (substr($route, 0, 5) == 'feeds' &&
        $loggedInUser->getUsername() != $routerParams['username']) 
    {
        $response = $e->getResponse();
        $response->setStatusCode(404);
        return;
    }

    if (!$e->getViewModel()->acl->isAllowed($userRole, $route)) {
        $response = $e->getResponse();
        $response->setStatusCode(404);
        return;
    }
}

This is the checkAcl() method. What we do in the first few lines is get the information about the route, we process the data about the identity of the user and the role. Because we only have two roles, by default the role is guest, unless the user is logged in. In that case, we set the role manually to member.

Products with more roles is a good idea to store the role of the user on the database and load it on the User entity, as we are storing the User entity as the identity of the user on the session. You will be able to easily check the role associated with a user and avoid assigning it manually.

After gathering the data, we have two big checks. The first one is a specific case for the feeds functionality. As users are members by default, they are allowed to see the feeds, modify the URLs, change the username for the other feed, and access the data of another user. In order to avoid this, we have several options. The first one we implement here is determine if we are working with feeds routes, and then check if the username the user is trying to access matches with his username. If they do not match, we redirect the user to a 404 page. The second option is not implemented here, but you definitely need to implement it in a ready-for-production application, checking the owner of the data you are accessing against the username trying to access it on all the methods, and of course, never trusting what is coming from the client.

The second big check we do here is to check if the role of the user is actually allowed to access the route we are processing. In case it is not allowed, we will redirect it to a 404 page.

The index.phtml file

Finally, to finish with the changes of this chapter, we need to modify the index.phtml page of the Wall module. As we are going to visit other people's walls, we don't want to allow the user to publish content on other's walls (they can only post comments).

What we have to do is check in the view if we are on our own wall or on another user's wall. We do this by using the following line of code:

<?php if ($isMyWall) : ?>

Of course, the $isMyWall variable should be populated from the controller. We do that by adding the following lines of code:

$viewData['isMyWall'] = !empty($loggedInUser)? 
$loggedInUser->getUsername() == $username : false;

Wall IndexController.php

As we already mentioned, you need to do changes in the IndexController.php file to determine if the wall the user is accessing is his or her own or another person's wall. Another thing you need to change in this file is the owner of the posted comments. Until now, all the comments were assigned to the current user; however, because the users now have the ability to log in, the comment posted should be credited to the right author. Again, this change is pretty straightforward and we will experiment with it and try to get it working.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset