samedi 26 juin 2010

Authentication and security with Play framework

Read this post in french

Today, let's see how to add authentication and security to a web application with Play, and what the framework offers with its Secure module.
We will study the following case: we have a public website, where you can browse without being authenticated. This site also has features of administration, displayed when someone identifies as an admin. To access these features, there is a URL that allows access to an authentication form.


Play allows to write user session information in a cookie. This cookie is signed, it is not editable on the client side, but is not encrypted, so you must not send sensitive information within it (no password for example). In our example, we want to use the session cookie to know if the user is identified as an admin or not.

One of the things we want to add to the web application, if the user is an administrator, is a "Delete" link in the html table that lists our business entities (e.g a list of music albums). We can therefore use the following code:


#{if session.get("username").equals("admin")}    
    Delete  
#{/if} 


But we quickly face a problem : this links leads to the following pattern of URL :
/admin/delete?id=11

Even if the link is hidden, anyone can enter this URL into his browser to delete the entity of his choice. We must also protect the delete method on the server side.
Play Secure module will allow us to do this elegantly. It also offers a ready to use login form that puts the information we need in the session cookie.

Let’s see how to configure the Secure module :

Begin by modifying the /conf/application.conf file :
  • Uncomment the following line  :
    module.secure=${play.path}/modules/secure
  • Add the following line to configure routes :
    # Import Secure routes
    *      /               module:secure
  • Add admin tokens :
    # Admin tokens
    application.admin=admin
    application.adminpwd=admin

Then, declare an admin controller for all activities that we want to restrict. Add @With annotation to this controller, to tell it that it must rely on the Secure module controller :


@With(Secure.class)
public class Admin extends Controller {
....
}


Add control on the delete action, using @Check annotation :

Check("admin")
public static void delete(Long id) {
...
}


Redefine the check method by creating a new class in the controller package, inheriting from Secure.Security class :


static boolean check(String profile) {
if(profile.equals("admin"))
return session.get("username").equals("admin");
return false;
}

This code will ask the Secure module to verify that the user in the session cookie is "admin" when the annotation @Check ("admin") will be found.

In the same class, redefine the authentify method. This method is used by the Secure module, to allow the user to see a page or not.

static boolean authentify(String username, String password) {
return Play.configuration.getProperty("application.admin").equals(username)&& Play.configuration.getProperty("application.adminpwd").equals(password);
}


With this configuration, if someone tries to enter /admin/delete?id=11 URL, he will arrive directly on the authentication form, to prove that he is an administrator. And of course if the password and the user entered are not good, an error is thrown.

Now, we would like to be able to go directly to this login form, to put information about user’s identity in the user session.

Just add the following code in the controller to expose the Admin login form to /admin/login URL :

public static void login() {
Application.list();
}

There will be a control for all methods in the Admin controller. So, using the login method, the user will be forwarded to the authentication form. Then if the credentials are OK, he will return to the main page of the application (albums list in this example).
Now we can modify the template to hide the admin links, using the secure.check tag :
 
 #{secure.check 'admin'}
     Delete
 #{/secure.check}

Finally, we want to allow a user identified as admin to disconnect from the application. Nothing could be simpler, just add a link to main.html template :

<body>
#{/secure.check}
<div align="right">
<a href="@{Secure.logout()}">Logout</a>
</div>
 #{/secure.check}
#{doLayout /}
</body>


That's it, now you know how to add administration and security features to a public website with Play!