RIFE : GuideAuthentication
This page last changed on Mar 29, 2008 by jpoteat.
Our growing example, the Friends database, is now a quite complete web application. However, the fact that anyone can add friends to it makes it a bit less useful. We'd like to be able to put the application on a web page and make it readable for anyone, but only allow for ourselves to edit the list. This means that we must add some kind of password protection, or authentication.
Handling authentication is a very common task and RIFE nicely abstracts the concept of users and their credentials. As you will see, there is built-in support both for keeping a list of users in an XML file, and more elaborate support for users in a database.
Most sites will want to keep their list of users in a database. Database users are discussed separately in the database users chapter. The remainder of this section uses memory users, for the sake of simplicity; your application will generally not need to be aware of where the user information came from, so you can use memory users during initial development and switch to database users once you need them. Some people also use memory users for setup or installation sections of a site that should only be accessible by a select few people that shouldn't change at run-time.
"Memory users" is the RIFE terminology for users that are kept in an XML file (which are kept in-memory after initialization, hence the name). This is the simplest way of organizing users, and is suitable for keeping track of a static group of users such as an administrator or perhaps a guest user.
With this method, you simply list the users and passwords in a file, specified as a ParticipantMemoryUsers in the repository. Let's start by adding the participant:
A memory users configuration file
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE rep SYSTEM "/dtd/rep.dtd"> <rep> <!-- Add users participant --> <participant param="rep/users.xml">ParticipantMemoryUsers</participant> <participant param="rep/config.xml">ParticipantConfig</participant> <participant param="rep/datasources.xml">ParticipantDatasources</participant> <participant param="sites/friends.xml">ParticipantSite</participant> </rep>
The file rep/users.xml is where the user list is defined. We only need one user, an administrator who can edit the friends list.
Memory users configuration file
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE credentials SYSTEM "/dtd/users.dtd"> <credentials> <user login="admin"> <password>password</password> <role>admin</role> </user> </credentials>
In addition to user name and password, there is a role tag, here set to admin. The role is used to grant access to different parts of the site to different users. We'll show how roles are used later in this chapter.
There are a few steps involved in setting up authentication, and the best way to explain it should be to go through the additions and changes to the application.
Since only the administrator will be able to make changes, there is no need to let visitors see those options. Splitting out the menu to its own subsite will be our first task.
A site in RIFE can be built from smaller subsites. Using subsites is a good way to make a site modular and it also makes it easier to reuse parts of a site in other applications. In this example, we'll put the administration part in its own subsite, and we'll also show another advantage of doing so when we add the authentication mechanism.
To achieve this, we simply move the elements to its own site file, sites/admin.xml, and add a subsite tag in the main site file, pointing to the subsite:
The main site definition file
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE site SYSTEM "/dtd/site.dtd"> <site> <arrival destid="DISPLAY"/> <element id="DISPLAY" file="display.xml" url="/display"> <flowlink srcexit="admin" destid="ADMIN"/> </element> <!-- Add a subsite --> <subsite id="ADMIN" file="admin.xml" urlprefix="/admin"/> </site>
Now locations starting with /admin will be handled by the administration subsite. After moving over the add, install and remove elements, the new site file looks like the following:
The admin site definition file
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE site SYSTEM "/dtd/site.dtd"> <site> <globalexit name="menu" destid="MENU"/> <arrival destid="MENU"/> <element id="MENU" file="admin/menu.xml" url="/menu"> <flowlink srcexit="install" destid="INSTALL"/> <flowlink srcexit="add" destid="ADD"/> <flowlink srcexit="remove" destid="REMOVE"/> <flowlink srcexit="back_to_display" destid=".DISPLAY"/> </element> <element id="INSTALL" file="admin/install.xml" url="/install"/> <element id="ADD" file="admin/add.xml" url="/add"/> <element id="REMOVE" file="admin/remove.xml" url="/remove"/> </site>
When referring back to the main site from the subsite, we set the destination to .DISPLAY. The dot is used as a separator, so the destination points to an element in the main site. Without the leading dot, links are relative, and refer to elements in the current subsite.
The dot seperates in a very similar way as the slash when you seperate directory names on your filesystem. For example, you could have written .ADMIN.REMOVE instead of REMOVE to refer to the removal element. Doing this however makes your site definition unflexible so it's generally discouraged.
Following the directory analogy, it's also possible to reference elements that are defined higher up the subsite hierarchy without using the leading dot. You're probably very familiar with the double-dot, .., which allows you to go to the parent directory. In RIFE, you can use the circumflex character: ^. Thus instead of writing .DISPLAY you could have used ^DISPLAY notation. Depending on your design this might again make your site structure more flexible and easier to cut up in seperated modules. Note that for file paths you have to seperate the double dot with slashes too. In RIFE element paths you just use the circumflex without dot seperators since they're completely redundant.
Now it's time to handle the actual authentication. The element for doing that is very simple to write. All we need to do is to extend a built-in element in RIFE, rife/authenticated/memory.xml:
The authentication element: elements/auth.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE element SYSTEM "/dtd/element.dtd"> <element extends="rife/authenticated/memory.xml"> <property name="template_name">auth.form</property> <property name="role">admin</property> <submission name="credentials"> <param name="login"/> <param name="password"/> </submission> <childtrigger name="authid"/> </element>
The built-in element handles all the authentication logic, including processing and displaying the login template. There are two properties, one for specifying which template file to use and one for the role. In this simple application, we only use one role, but it can be a very powerful tool when there is a need for fine-grained access control to different parts of a site.
The submission parameters of the element are defined as usual, but the childtrigger tag is something new. We'll describe its purpose in a moment, but first we need to add a form for the login submission.
We need a template with a form for filling in the user name and password. There should be no surprises here, just a regular form with a submission named credentials:
The authentication template
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head><title>Admin</title></head> <body> <table cellpadding="5" cellspacing="0"> <tr valign="top" nowrap="1"> <td width="100%"> <h3>Please provide a login and password</h3> <!--I 'common.error_area'/--> <form name="credentials" action="[!V 'SUBMISSION:FORM:credentials'/]" method="post"> <!--V 'SUBMISSION:PARAMS:credentials'/--> login<br/> <input name="login" value="[!V 'PARAM:login'][!/V]" type="text" size="18" maxlength="10" /><br/> password<br/> <input name="password" type="password" size="18" maxlength="10" /><br/> <input type="submit" value="Login" /><br/> </form> </td> </tr> </table> </body> </html>
We have restructured the site to have an administration section, and we have an element that handles authentication. Now we are ready to put things together. We are going to do this by using inheritance, which is a very powerful feature of RIFE.
An element or subsite in a RIFE application can inherit another element. From now on, we'll call the element that inherits "child", and the one that is inherited "parent". The way inheritance works is that under certain circumstances, the processElement method of the parent will be invoked instead of the child one.
RIFE handles this in a very smart way and makes this process completely invisible to the child element. The parent element can be part of a full-blown site structure. It can activate exits, accept and process submissions, go to other elements that use inheritance too, ... Every single feature of RIFE's web engine is at your disposal in the parent. You're even able to inherit from an entire isolated subsite if it has an arrival element! Once you'll give the command to break out of the inheritance, the child is activated and receives exactly the parameter values that it was supposed to receive in the first place.
In our case, we will make the admin site inherit the authentication element. When the user is not logged in, the parent element will be displayed, and otherwise the child, that is the admin site, will be displayed.
The RIFE engine determines which of the parent and child to use through something called a "childtrigger". We mentioned child triggers very briefly, the authentication element. The childtrigger tag in the parent element specifies the name of a variable or cookie to use for determining which element to run. If the trigger is not set, the parent's processElement method is called directly. If it is set, the method childTriggered of the parent element is called, where we can check if the value of the trigger is valid or not.
This might sound a bit complicated, but fortunately all of it is implemented in the memory users authentication element that we extended, so we don't need to handle it. It's still useful to know what happens behind the scenes, and for a more advanced application it might be necessary to implement a custom element that handles child triggers.
The child trigger in our authentication element is a variable called authid. We'll add it as a global variable to the site definition in sites/friends.xml to make it available from the elements:
Example 8-7. Adding authid to sites/friends.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE site SYSTEM "/dtd/site.dtd"> <site> <!-- Add authid variable --> <globalvar name="authid"/> <!-- Refer to the AUTH element definition --> <element id="AUTH" file="elements/auth.xml" /> ... </site>
Now we're all set for the last step, making the administration subsite inherit the authentication element.
We still haven't made any changes to the original friends application, except for splitting it up in a main site and an administration subsite. As a matter of fact, that is the beauty of using inheritance: there is no need to rewrite or add to the element or subsite that is to become the child. All we need to do is to modify the subsite definition in sites/friends.xml:
<subsite id="ADMIN" file="admin.xml" urlprefix="/admin" inherits="AUTH"/>
After adding the property inherits="AUTH", the admin site will be password protected, with no other changes required.
The Friends database is still a little rough around the edges. There is no way to logout, for instance, and there's no indication showing whether the user is logged in or not on the main site. We'll add a logout link and then leave the rest of the polishing work as an exercise for the reader.
The built-in authentication element we've used comes with a logout element as well. Logging out is as simple as adding the element to the site and a link to it in the template.
<element id="LOGOUT" file="rife/logout/passthrough/memory.xml" url="/logout"> <flowlink srcexit="logged_out" destid="DISPLAY"/> </element>
The admin menu seems like a good place for the logout link:
<p><a href="[!V 'EXIT:QUERY:logout'/]">logout</a></p>
The logout element simply resets the child trigger.
Now we've seen how inheritance makes it easy to add authentication to an otherwise finished application with very little intervention. This means that an application can be developed, tested and debugged without authentication, and then by just dropping in the inheritance parameter and have instant authentication support.
|Document generated by Confluence on Oct 19, 2010 14:57|