Chapter 6. Leveraging the Play Stack – Security, Internationalization, Cache, and the HTTP Client

In this chapter, you will learn how to protect your application against common web attacks, such as cross-site scripting and cross-site request forgery. You will also learn how to restrict some pages of your application to authenticated users only. Then, you will learn how to internationalize the application, how to use the cache to improve performance, and how to perform HTTP requests to other web services.

The following topics will be covered in this chapter:

  • Security (cross-site scripting, cross-site request forgery, authentication, and HTTPS)
  • Cache
  • Internationalization
  • The HTTP client

Handling security concerns

This section presents the main security challenges in web applications and how to handle them with the Play framework.

Authentication

In the previous chapter, we added a page that showed an auction room for an item. The form to participate in an auction requires users to fill their name and a price for the item. In this section, I propose to restrict auction rooms to authenticated users only. This means that if a non-authenticated user tries to go to an auction room, he is redirected to a login form. Once he is logged in, he is redirected back to the auction room, whose form now has only one field, the bid price, because the username can be retrieved from the user's identity.

To differentiate between identified and non-identified users, we rely on a session mechanism. Once a user is authenticated, he visits the pages of the application on behalf of his identity; two users might not see the same response when they go to the same page. To achieve this in a stateless way, the user's session is not stored on the server but on the client so that two users going to a same page get different responses because their requests are effectively different. Concretely, Play uses cookies to store the user's session. To prevent malicious users from forging a fake session, the session's cookie cannot be modified in JavaScript, and above all, its content is signed using a passphrase defined in the application's configuration (more precisely, defined by the application.secret key). As long as your application's secret key is not shared with the outside world, it is very difficult for the outside world to forge a fake session.

Back to our shop application, to restrict auction rooms to authenticated users, we will need to achieve the following:

  • Add a login form and add actions to authenticate the user and to log out
  • Redirect anonymous users to the login form when they try to go to an auction room
  • Retrieve the username from its session when processing its bid
  • Add a logout link in the application to log out users

Let's start by adding the following endpoints to the routes file:

GET   /login     controllers.Authentication.login(returnTo)
POST  /authenticate
                 controllers.Authentication.authenticate(returnTo)
GET   /logout    controllers.Authentication.logout

The Authentication.login action returns the HTML login form. The form submission is bound to the Authentication.authenticate action, which checks whether the username and password are correct and in such a case, adds information on the user's identity to his session. The Authentication.logout action removes the user's identity from his session. The login and authenticate actions both take a returnTo parameter that defines which URL the user should be redirected to in the case of a successful authentication.

Our login form has two fields: a username and a password. Both are text and required fields. The corresponding form model can be defined as follows:

type Login = (String, String)
object Login {
  val form = Form(tuple(
    "username" -> nonEmptyText,
    "password" -> nonEmptyText
  ))
}

In Java, an equivalent form model can be defined by the following class:

public static class Login {
    @Constraints.Required
    public String username;
    @Constraints.Required
    public String password;
}

Finally, the associated HTML form can be simply defined as follows:

@(form: Form[Authentication.Login], returnTo: String)
@layout {
  <h1>Please sign in</h1>
  @helper.form(routes.Authentication.authenticate(returnTo)) {
    <ul>
      @for(error <- form.globalErrors) {
        <li>@error.message</li>
      }
    </ul>
    @helper.inputText(form("username"), '_label -> "Name")
    @helper.inputPassword(form("password"), '_label -> "Password")
    <button>Sign in</button>
  }
}

The template takes the form model and return URL as parameters and returns a page that contains a form bound to the authentication action.

The login action is straightforward to implement—it just renders the previous template. The authenticate action checks whether the form has been correctly filled in and that the credentials are correct and in such a case, adds the user identity to his session and redirects him to the return URL. Then, checking whether a user has been identified is just a matter of checking whether it has identity information in his session. Here is an implementation of the authenticate action:

def authenticate(returnTo: String) = Action { implicit request =>
  val submission = Login.form.bindFromRequest()
  submission.fold(
    errors => BadRequest(views.html.login(errors, returnTo)),
    {
      case (username, password) =>
        if (users.authenticate(username, password)) {
          Redirect(returnTo).addingToSession("username" -> username)
        } else {
          val erroneousSubmission = submission
            .withGlobalError("Invalid username and/or password")
          BadRequest(
            views.html.login(erroneousSubmission, returnTo)
          )
        }
    }
  )
}

In the preceding code, the highlighted parts are the ones that check whether the credentials are valid (using a service layer named users) and the one that adds the username to the user's session. The session can be seen as a key-value store. In our case, we associate the user's name with the username key (so we assume that names are unique among users).

The Java version is as follows:

public static Result authenticate(String returnTo) {
  Form<Login> submission =
      Form.form(Login.class).bindFromRequest();
  if (submission.hasErrors()) {
    return badRequest(
        views.html.login.render(submission, returnTo)
    );
  } else {
    Login login = submission.get();
    if (users.authenticate(login.username, login.password)) {
      session().put("username", login.username);
      return redirect(returnTo);
    } else {
      submission.reject("Invalid username and/or password");
      return badRequest(
        views.html.login.render(submission, returnTo)
      );
    }
  }
}

Now that we have support to authenticate users, we can restrict some actions to authenticated users only. To achieve this, we look in the session for the username key. Here is how we can restrict the auction room to authenticated users:

def room(id: Long) = Action { implicit request =>
  request.session.get("username") match {
    case Some(username) =>
      shop.get(id) match {
        case Some(item) => Ok(views.html.auctionRoom(item))
        case None => NotFound
      }
    case None =>
      Redirect(routes.Authentication.login(request.uri))
  }
}

The request.session expression returns the user's session. Then, the get method searches in the session for a value associated with a given key (in our case, username). If there is no such value, we redirect the users to the login action and pass it the current request URL as a parameter.

The Java equivalent is as follows:

public static Result room(Long id) {
  String username = session().get("username");
  if (username != null) {
    Item item = Shop.get(id);
    if (item != null) {
      return ok(views.html.auctionRoom.render(item));
    } else return notFound();
  } else {
    return redirect(routes.Authentication.login(request().uri()));
  }
}

Note

In Scala, reading the session is achieved by calling the session member of the request (that reads the request's session cookie), and modifying the session is achieved by using methods such as addingToSession or removingFromSession on a result (that writes the result's session cookie).

In Java, the API to manipulate the session is somewhat more high level; reading from and writing to the session is achieved by retrieving the session with the session method of the controller and then by imperatively using its get, put, or remove method.

Finally, the logout action removes the user's identity from its session:

val logout = Action { implicit request =>
  Redirect(routes.Items.list()).removingFromSession("username")
}

The Java equivalent is the following:

public static Result logout() {
    session().remove("username");
    return redirect(routes.Items.list());
}

Cross-site scripting

Cross-site scripting (XSS) is a typical security vulnerability of web applications that allows attackers to inject a snippet of JavaScript into the HTML pages of your application. This snippet is then executed by all the clients of your application that browse the infected page. By default, dynamic values inserted in HTML templates are escaped so that their content can not be interpreted as HTML structure by browsers. Concretely, for example, the @("<a>foo</a>") expression in a template produces the &lt;a&gt;foo&lt;/a&gt; output.

There is a way to disable the HTML escaping process by using the Html function; @Html("<a>foo</a>") produces the <a>foo</a> output. In order to protect your application against cross-site scripting attacks, you should never pass a user-submitted value to the Html function. More generally, I recommend avoiding the use of this function.

Cross-site request forgery

Cross-site request forgery (CSRF) is another typical security vulnerability of web applications. It consists of making a user invoke an action of your application, unbeknownst to him. An attacker can achieve this, for instance, by including, in an HTML document, an image that points to an URL of your application. When a user loads the page of the attacker's website, the request performed to your application to retrieve the image content, includes all the user's cookies for your application, including their session cookie. Note that in this case, the HTTP request is issued using the GET verb, but the attacker can perform a POST request using a form targeting your application (though this will require an additional action from the user). In such a case, the request content type is limited to application/x-www-form-urlencoded, multipart/form-data, and text/plain.

To prevent this kind of attack, a simple rule is to not expose actions that have side effects as GET routes. However, this is not sufficient because, as explained previously, the attacker could display to the user a fake form that would perform a POST request to your application. You can protect your application against these attacks by adding a hidden field to your forms that contains a randomly generated value and then by checking whether the field contains the expected value when you process the form submission. Attackers will not be able to send you fake requests with the correct value. Finally, because this value is randomly generated and specific to a user, you have to store it in his session before displaying the form.

For instance, to protect the Items.create action, you must first modify the Items.createForm action, which displays the form to create items so that it generates a random token and adds it as a hidden field of the displayed form and to the user's session. Then, you can protect the Items.create action by checking whether the form field effectively contains the same token as in the user's session.

Play provides an HTTP filter to automate this process.

HTTP request filters

HTTP filters is a feature of the Play framework that makes it possible to run some code before or after your actions are invoked. Filters are defined in the application's global object and are applied to all routed action right before the action's code is executed.

In Scala, you can define a filter by overriding the doFilter method of your global object, which has the following signature:

def doFilter(action: EssentialAction): EssentialAction

This method is called by Play before invoking your actions. The default implementation does nothing; it just returns the action.

The EssentialAction type is slightly more general than the Action[A] type that we have been using from the beginning of this book. It is defined as follows:

trait EssentialAction extends
    RequestHeader => Iteratee[Array[Byte], Result]

This means that an essential action is a function that takes the HTTP request headers and returns an incremental computation (processing the request body), yielding an HTTP response.

A basic filter can be defined as follows:

import play.api.mvc.EssentialAction
override def doFilter(action: EssentialAction) =
  EssentialAction { headers =>
    println("do something before the action is executed")
    val iteratee = action(headers)
    println("when is this printed?")
    iteratee
  }

Note that the second println method is not executed after the action has been executed because the computation that yields the action's result is asynchronous. To do something after an action has been executed, you can use the map method:

action(headers).map { result =>
  println("do something after the action has been executed")
  result
}

For convenience, Play provides a higher-level Filter API that hides the iteratee-related details. Thus, our filter can be implemented as follows:

import play.api.mvc.Filter
class MyFilter extends Filter {
  def apply(action: RequestHeader => Future[Result])
           (headers: RequestHeader) = {
    println("do something before the action is executed")
    action(headers).map { result =>
      println("do something after the action has been executed")
      result
    }
  }
}

Then, to apply such a filter to your global object, you can make this one extend the WithFilters class:

import play.api.mvc.WithFilters
object Global extends WithFilters(new MyFilter) {
  // … the remaining global object definition
}

The WithFilters class overrides the doFilter method to apply the filter that is passed as a parameter. You can pass several filters to the WithFilters constructor:

object Global extends WithFilters(
  new FirstFilter, new SecondFilter
)

In such a case, filters are chained in the same order they are passed as parameters. In the preceding code, FirstFilter is applied before SecondFilter.

In Java, you can set up the filters of your application by overriding the filters method of your global object, which returns an array of filter classes:

import play.api.mvc.EssentialFilter;
@Override
public <T extends EssentialFilter> java.lang.Class<T>[] filters()
{
  return new Class[] { MyFilter.class };
}

Nevertheless, at the time of writing this, there is no Java-idiomatic API to define filters.

Play provides some predefined filters. To use them, add a dependency on the filters artifact to your build:

libraryDependencies += filters

Notably, Play provides a filter that compresses the results of your actions if the client accepts the gzip compression. To use it, just add the following to your global object definition:

import play.filters.gzip.GzipFilter
object Global extends WithFilters(new GzipFilter()) {
  // …
}

The Java equivalent is as follows:

@Override
public <T extends EssentialFilter> java.lang.Class<T>[] filters()
{
  return new Class[] { GzipFilter.class };
}

The following figure integrates filters and the user session in the architecture of the Play framework:

HTTP request filters

Filters are located between the router and the controllers. The user session exists only on the client side.

Using the CSRF filter

Finally, Play also provides a CSRF filter that automatically checks that the CSRF token of a form submission corresponds to the one in the client's session.

Enable this filter as usual:

import play.filters.csrf.CSRFFilter
object Global extends WithFilters(new CSRFFilter())

The Java equivalent is as follows:

@Override
public <T extends EssentialFilter> java.lang.Class<T>[] filters()
{
  return new Class[] { CSRFFilter.class };
}

The filter generates a new token for each GET request and puts it to the client's session. Then, by default, all POST requests that contain a form submission are filtered; if the form submission does not contain a CSRF field with the correct token value, the filter returns a 403 (Forbidden) response.

So, you have to add a CSRF field that contains the generated token to each of your forms. Again, Play provides a function that does just this:

@helper.form(routes.Items.create()) {
  @helper.CSRF.formField
  … the remaining form definition
}

Alternatively, you can put the token in the query string of the form submission action:

@helper.form(helper.CSRF(routes.Items.create())) { … }

In Scala, in both cases, you need to add an implicit RequestHeader parameter to your template so that the CSRF helper can retrieve the current CSRF token:

@(form: Form[CreateItem])(implicit header: RequestHeader)

This step is not required in Java because the current request's header is automatically imported from the current HTTP context.

Note

The HTTP context is set up by Play before executing your action's code. It basically contains references to the incoming HTTP request and the outgoing HTTP response.

See the relevant part of the official documentation at http://www.playframework.com/documentation/2.3.x/ScalaCsrf to get information on all the possible configuration options of the filter.

Enabling HTTPS

By default, Play applications use only HTTP. This means that the data exchanged with clients can be seen by inspecting the network traffic. This situation can be acceptable for a wide range of applications; however, as soon as you exchange sensible data with clients, such as passwords or credit card numbers, the communications should be encrypted. You can achieve this by using HTTPS instead of HTTP. Enabling the HTTPS support is just a matter of defining a system property named https.port that contains the port number to be used. Note that such a property can also be passed as an argument to the run or start sbt command:

[shop] $ run –Dhttps.port=9001

You can then access your application's resources using HTTPS URLs, such as https://localhost:9001/items.

HTTPS uses SSL to encrypt the communications between clients and servers. The SSL protocol requires servers to own a certificate. Play generates a self-signed certificate if you don't provide one. However, web browsers generally warn users when they encounter such a certificate, so you should consider buying a certificate from a signing authority. To use such a signed certificate in your Play application, store it in a Java KeyStore and set an https.keyStore system property that contains the path to the KeyStore and an https.keyStorePassword system property that contains the KeyStore password:

[sohp] $ run –Dhttps.port=9001 -Dhttps.keyStore=/path/to/jks -Dhttps.keyStorePassword=password

Tip

Play builds an SSLEngine setting up SSL according to the system properties described earlier. You can have even finer control by providing your own SSLEngine. Just implement the play.server.api.SSLEnineProvider class (or play.server.SSLEngineProvider in Java) that has only one abstract method that returns an SSLEngine. Then, tell Play to use it by defining an https.sslengineprovider property containing the fully qualified name of your SSLEngineProvider implementation.

If you want to disable HTTP, just set an http.port system property to disabled:

[shop] $ run –Dhttps.port=9001 –Dhttp.port=disabled

Finally, absolute URLs generated by the reverse router must now use HTTPS or WSS (in the case of WebSockets). This can be achieved by setting the secured parameter value to true:

routes.Application.index.absoluteURL(secured = true)

The equivalent JavaScript code is as follows:

routes.controllers.Application.index.absoluteURL({secured: true})

In the case of WebSockets, the JavaScript code is as follows:

routes.controllers.Auctions.channel.webSocketURL({secured: true})
..................Content has been hidden....................

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