The goal of this tutorial is to learn how to use Lion's security framework to build a typical login process using PHP.
These kinds of applications are really easy to develop but extremely hard to maintain in PHP without a solid and fine-grained security layer.
In this tutorial we are going to:
Build a login form and integrate it within the security framework
Define roles and permissions
Protect pages and sections to be accessible just by authenticated users
The application that we are going to develop will expose a login form, allowing users to attempt to login and authenticate themselves. We're going to also protect some pages in order to be accessible just by users that have permission to view the page.
In the source code package, you will find the following directory structure and classes:
Here's a summary of the classes we've defined in the example source code:
2 controllers: one for the login page and one corresponding to a protected page.
1 event handler: in charge of handling the submit from the login page
2 model classes: one required by the security framework (the user loader) and the other one defining the model service we're going to consume from outside the model in order to perform the login
Defining roles and permissions
First, we want to define permission roles for our application. The concept of a role is similar to the concept of a user group. In fact, we use roles to group users having the same permissions.
i.e. we can define an ADMIN role, so all the users having this role will also have the same permissions.
For the purpose of our tutorial, we are going to just define a single role: REGISTERED_USER.
A user with the REGISTERED_USER role will be considered a registered user.
Once we have defined a role, we are then able to associate permissions with it. The concept of a permission is similar to the concept of a key that opens a door.
In our example, we are going to protect pages by only giving access to users having the correct permission, similar to a door accessible only for users having the key.
We define a single permission level called the PRIVATE_AREA_PERMISSION and we assign it to the REGISTERED_USER role.
In order to define this role, let's take a look at the app/config/security.xml file:
<?xml version = "1.0" standalone="yes"?>
<configuration>
<role-definitions>
<!-- REGISTERED_USER is a role for registered users -->
<role id="REGISTERED_USER">
<permissions>
<permission id="PRIVATE_AREA_PERMISSION"/>
</permissions>
</role>
</role-definitions>
<permission-definitions>
<!-- PRIVATE_AREA_PERMISSION is the needed permission
to be able to access to the private area of
our website -->
<permission id="PRIVATE_AREA_PERMISSION"/>
</permission-definitions>
</configuration>
The Login Page
Let's use our knowledge we've gained from the earlier tutorials and add a login page to our web application. To do that, we'll add a controller and a template.
The controller can be seen in the source code (/libs/controllers/LoginController.class.php):
Now, let's see how our login page is shown in a browser. To do that, just execute the url http://yourdomain/login.html within your browser:
Suppose you want to add some validation checks to the user's input. Lion provides some very simple operations to achieve this.
In our example, we use the Component's validationrule to validate the login and password fields:
Neither the login or password are blank (i.e. they are both mandatory)
The login is not more than 10 characters nor is the password more than 20 characters
Login Event Handling
The last stage of handling the login process is to define the submit event to actually login in the user. To handle the submission, we're going to define an event handler associated to the 'login' view.
Following the default naming convention, the event handler is named LoginEventHandler and is found in the file: /libs/eventhandler/LoginEventHandler.class.php:
When the submit event happens and the form is submitted, Lion will intercept the call and the validation steps, mentioned above, will be done on the server side.
The values contained in the login and password components will contain the user's input.
This can all be seen in lines 7-10 in the LoginEventHandler class, where we retrieve both values from the components and pass them to the login model service.
The Login Model Service
From the previous section, we see how Lion intercepts a login submission from a user.
If you follow the code seen in /libs/eventhandler/LoginEventHandler.class.php, you will notice we use Lion's Model Service (which hopefully you have already read about).
By utilizing Lion's Model Service, we abstract away which class is doing all of the work, so there's no need for an explicit "new ..." call.
If you recall, we define our Model Services by creating the information in our /app/config/model_services.xml file:
<?xml version = "1.0" standalone="yes"?>
<configuration>
<model-services>
<class name="UserServices">
<service name = "logon"
class-method = "logon"/>
</class>
</model-services>
</configuration>
Now, we need to create this UserServices class in order to declare a bridge method to perform the actual logon.
In our UserServices class, we make a call into Lion's __AuthenticationManager::logon() method.
In just a moment, we will see how to setup our configurations and classes to actually do our own user authentication, but first let's conceptualize the process visually:
At this point, if you still have not read about Lion's Authentication section, it is highly recommended to do so: Authentication in Lion.
We need some way to tell Lion about which class will be handling our authentication. Thus, we integrate our user loader as the user loader used by the security framework to logon users.
The only thing we have to do is setup the /app/config/context.xml by setting the following configuration:
In our example, we will construct our own user loader class that implements the __IUserLoader interface, which requires you to implement the __IUserLoader::loadUser() method.
This is what will be called by Lion's Authentication Framework.
Let's take a look at our user loader implementation in the /libs/model/CustomUserLoader.class.php:
<?php
class CustomUserLoader implements __IUserLoader {
//valid login and passwords:
private $_user_and_passwords = array(
'aparraga' => 'secret1',
'ckop' => 'secret2',
'dparraga' => 'secret3',
'goofy' => 'secret4'
);
/**
* Get the user identity and returns a user corresponding
* to the given identity.
*
* Note that the returned user contains his
* credentials in order to be used by the security
* framework
*
*/
public function &loadUser(__IUserIdentity $user_identity) {
The entire purpose of the class is to authenticate the user with the user identity. In our example, we have a simple array with user names and passwords for which to retrieve the real password.
In a real world example, you would probably be going to a database to retrieve this password with the given user name, rather than having a static array.
In fact, we will show an example of this later in this Tutorial.
Because we are identifying users by the login/username, the user identity will just contain the login string.
It's this class's responsibility to return a Lion Framework instance of a __User object. If no user exists with the given login, then we return null.
Another thing to take notice of is how the user loaded sets roles to users that we find a name for in the database.
Here's a summarized list of what we just accomplished in this section.
The model service will call the authentication manager in order to perform the authentication
The authentication manager will delegate (as we have configure in the context.xml) to our user loader in order to get the user
After the user loader returns a user instance, the authentication manager will check the given credentials with the user credentials
Finally, if credentials match, the authentication manager will set the user as authenticated and return a reference to it
Protecting pages
Another requirement of a good web framework is being able to manage the protection of certain pages. In order to accomplish this, Lion allows us to set permissions to controllers that we want to protect.
We have a controller named ProtectedPageController located in /libs/controllers/ProtectedPageController.class.php as the following:
Only users with the PRIVATE_AREA_PERMISSION permission
will be able to access to this page.
<br><br>
As soon as the role REGISTERED_USER contains this permission,
users with this role will be able to access to this page.
By using the definitions we setup in the very first section of this tutorial, let's assign the PRIVATE_AREA_PERMISSION to the ProtectedPageController controller in the /app/config/controllers.xml:
<?xml version = "1.0" standalone="yes"?>
<configuration>
<controller-definitions>
<!-- generic rule -->
<controller code = "*" class = "*Controller"/>
<controller code = "protectedPage"
class = "ProtectedPageController">
<permission id = "PRIVATE_AREA_PERMISSION"></permission>
</controller>
</controller-definitions>
</configuration>
Now the page http://yourdomain/protectedPage.html will only be accessible for users that have the PRIVATE_AREA_PERMISSION permission.
In our example, this translates to users having the REGISTERED_USER role.
In order to see that our protected controller protectedPage is already protected, try to execute it from your browser without being logged-in (remember to clear the cache):
This page indicates that lion has detected an invalid user attempting to access a protected page without having the required permission.
By default, the __ActionController will throw an Exception when a user without the correct permissions attempts to access the page.
However, Lion allows us to customize our behavior in the case that the page is accessed without the required permission.
To do so, we just need to override the onAccessError method within the ProtectedPageController class (i.e. to redirect to the login page):
Now, when we try to access to the protectedPage.html, the application will send us to the login page.
Certainly, we could improve the functionality by adding some error messaging, but for the purpose of this tutorial this is enough.
Executing the application
Now, let's execute our application. Write the following url within your browser: http://yourdomain/login.html
Now try by entering a non existent login/password and see how the application redirects you again to the login page.
Now, try with a valid login/password. i.e. aparraga/secret1:
Retrieving users from a database
As we mentioned before, most authentication frameworks use a database to authenticate a user. Here we illustrate how to load our users from a database.
If you are familiar with a DAO design pattern, you might recognize that our user loader is more like a DAO in charge of loading users.
Thus, let's improve the original user loader class in order to get users from a data source.
First, let's see a simple example of structuring user information in a database.
We want to have users with roles; this suggests to us that we want a user table and a roles tables as shown in the following diagram:
In short, we want to associate users to a set of roles so that we can dynamically load them for a given user. Take a look at our table definitions in "database language":
--
-- Table structure for table `roles`
--
CREATE TABLE `roles` (
`role` varchar(32) NOT NULL,
`user_id` int(32) NOT NULL,
PRIMARY KEY (`role`,`user_id`)
);
-- ------------------------------------
--
-- Table structure for table `users`
--
CREATE TABLE `users` (
`id` int(32) NOT NULL auto_increment,
`login` varchar(150) NOT NULL,
`password` varchar(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `login` (`login`)
);
Let's change our previous user loader to now retrieve user and roles from the database:
<?php
class CustomUserLoader implements __IUserLoader {
/**
* Get the user identity and returns a user corresponding
* to the given identity.
*
* Note that the returned user contains his
* credentials in order to be used by the security
* framework
*
*/
public function &loadUser(__IUserIdentity $user_identity) {
Notice how easy it is to then assign the set of roles to a given user? Just set the array of roles on the the Lion __User object.
How to perform a logout
To perform a logout is really simple with the security framework: just call to the __AuthenticationManager::getInstance()->logout() method.
The default behavior of the __AuthenticationManager's logout method is to unset the User object, reset the User in the session, and then login in as an Anonymous User.