Enterprise Java

Play! Framework Security Example

Today, security has become a priority in most web applications. Even giant enterprises have security issues that are found from time to time. The first barrier to avoid this issues is a login page to avoid people from seeing information they are not meant to see. Also, there are headers that mitigate security issues and provide an extra level of defence for new applications.

In this example, we’ll write a login page, and forbid access to secured pages to users which are not logged in. Then we’ll configure security headers, such as: X-Frame-Options, X-XSS-Protection, X-Content-Type-Options, X-Permitted-Cross-Domain-Policies and Content-Security-Policy. And finally, we’ll configure CORS. I assume the reader has knowledge about hashing, CORS and security headers (or at least the capabilities to research), I will give a shallow explain to these concepts, but further research will be needed if it’s not clear after that.

All this will be done using typesafe activator 1.2.12, sbt 0.13.5, java 1.8.0_66, scala 2.11.6 and Play 2.4.3. Have a look at this example of how to write a Play application, and come back to make it secure!

1. Authentication

Writing authentication for a Play! application is not that hard. The process is pretty simple, the first thing we need is to write a page to create a user, and store the password hashed.

For the hashing process we’ll use BCrypt, so add this line "org.mindrot" % "jbcrypt" % "0.3m" to your library dependencies in your build.sbt.

scala’s build.sbt

name := """play-security-scala"""

version := "1.0-SNAPSHOT"

lazy val root = (project in file(".")).enablePlugins(PlayScala)

scalaVersion := "2.11.6"

libraryDependencies ++= Seq(
  jdbc,
  cache,
  ws,
  "org.mindrot" % "jbcrypt" % "0.3m",
  specs2 % Test
)

resolvers += "scalaz-bintray" at "http://dl.bintray.com/scalaz/releases"

// Play provides two styles of routers, one expects its actions to be injected, the
// other, legacy style, accesses its actions statically.
routesGenerator := InjectedRoutesGenerator

java’s build.sbt

name := """play-security-java"""

version := "1.0-SNAPSHOT"

lazy val root = (project in file(".")).enablePlugins(PlayJava)

scalaVersion := "2.11.6"

libraryDependencies ++= Seq(
  javaJdbc,
  cache,
  "org.mindrot" % "jbcrypt" % "0.3m",
  javaWs
)

// Play provides two styles of routers, one expects its actions to be injected, the
// other, legacy style, accesses its actions statically.
routesGenerator := InjectedRoutesGenerator

Then we make our model, which will be a User and a Session, in a package named model.

User.scala

package model

case class User(name: String, password: String)

User.java

package model;

public class User {

    private final String name;
    private final String password;

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public String getPassword() {
        return password;
    }
}

As you see, user’s model is pretty simple. It has a name and a password.

Session.scala

package model

import java.util.Date

case class Session(user: User, loggedIn: Date, lastSeen: Date)

Session.java

package model;

import java.util.Date;

public class Session {

    private final User user;
    private final Date loggedIn;
    private final Date lastSeen;

    public Session(User user, Date loggedIn, Date lastSeen) {
        this.user = user;
        this.loggedIn = loggedIn;
        this.lastSeen = lastSeen;
    }

    public User getUser() {
        return user;
    }

    public Date getLoggedIn() {
        return loggedIn;
    }

    public Date getLastSeen() {
        return lastSeen;
    }
}

Sessions are composed by a user, a logged in date and a last seen date.

We’ll use the lastSeen date, to make max inactivity time restriction, so now we configure it in application.conf as user.inactivity.max=7200000 #Two Hours.

Now we need a storage for this model. For this example, we’ll just store them on a map on memory.

SessionRepository.scala

package repository

import java.util.Date

import model.{User, Session}
import play.api.Play
import play.api.Play.current

import scala.collection.concurrent.TrieMap

/**
  * Created by svinci on 06/12/15.
  */
trait SessionRepository {
  def isLoggedIn(name: String): Boolean
  def login(user: User): Unit
  def logout(name: String): Unit
}
object SessionRepository extends SessionRepository{

  private val repo: TrieMap[String, Session] = TrieMap()
  private val ttl: Long = Play.application.configuration.getLong("user.inactivity.max").get

  def isLoggedIn(name: String): Boolean = {
    val maybeSession: Option[Session] = repo.get(name)
    val result = maybeSession.exists(s => {
      val now: Long = new Date().getTime
      val lastSeen: Long = s.lastSeen.getTime
      now - lastSeen < ttl 
    }) 
    if (result) { repo.put(name, maybeSession.get.copy(lastSeen = new Date())) } 
    result 
  } 
  def login(user: User): Unit = { 
    isLoggedIn(user.name) match { 
      case true => throw new IllegalArgumentException("user is already logged in")
      case false =>
        val now: Date = new Date()
        repo.put(user.name, Session(user, now, now))
    }
  }

  def logout(name: String): Unit = repo.remove(name)

}

SessionRepository.java

package repository;

import com.google.common.base.Preconditions;
import model.Session;
import model.User;
import org.apache.commons.lang3.StringUtils;
import play.Play;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * Created by svinci on 06/12/15.
 */
class SessionRepository {

    private final Map<String, Session> repo;
    private final long ttl;

    public static final SessionRepository instance = new SessionRepository();

    private SessionRepository() {
        this.repo = new HashMap<>();
        this.ttl = Play.application().configuration().getLong("user.inactivity.max");
    }

    public boolean isLoggedIn(String name) {
        final Session session = repo.get(name);
        final boolean loggedIn = Optional.ofNullable(session).map(s -> {
            Date now = new Date();
            final long inactivityPeriod = now.getTime() - s.getLastSeen().getTime();
            return inactivityPeriod < ttl;
        }).orElse(false);

        if (!loggedIn) repo.remove(name);
        else repo.put(name, new Session(session.getUser(), session.getLoggedIn(), new Date()));

        return loggedIn;
    }

    public void login(User user) {
        Preconditions.checkArgument(!isLoggedIn(user.getName()), "user is already logged in");
        final Date now = new Date();
        repo.put(user.getName(), new Session(user, now, now));
    }

    public void logout(String name) {
        repo.remove(name);
    }

}

Session reporitory defines three methods: isLoggedIn, login and logout.

The method isLoggedIn receives a user name, and retrieves the session assigned to it in our map. If there is any, it checks it’s not expired. If everything is correct it returns true, else false.

The method login makes a simple check to make sure the user is not already logged in, and, if it passes, it just creates a new session with the current date as logged in and last seen and inserts it in our map.

The method logout just removes the session assigned to that user (if any).

UserRepository.scala

package repository

import model.User
import org.mindrot.jbcrypt.BCrypt

import scala.collection.concurrent.TrieMap

trait UserRepository {
  def create(name: String, password: String): User
  def login(name: String, password: String): Boolean
  def logout(name: String): Unit
  def isLoggedIn(name: String): Boolean

}

object UserRepository extends UserRepository {

  private val sessionRepository: SessionRepository = SessionRepository

  private val repo: TrieMap[String, User] = TrieMap()
  private val salt: String = BCrypt.gensalt()

  private def hash(v: String): String = BCrypt.hashpw(v, salt)

  private def compare(v: String, hashed: String): Boolean = hash(v) == hashed

  override def create(name: String, password: String): User = {
    repo.get(name) match {
      case Some(_) => throw new IllegalArgumentException("duplicated user name")
      case None =>
        val user = User(name, hash(password))
        repo.put(name, user)
        user
    }
  }

  override def isLoggedIn(name: String): Boolean = this.sessionRepository.isLoggedIn(name)

  override def logout(name: String): Unit = this.sessionRepository.logout(name)

  override def login(name: String, password: String): Boolean = repo.get(name) match {
    case None => false
    case Some(u) =>
      val pwdMatch = compare(password, u.password)
      if (pwdMatch) sessionRepository.login(u)
      pwdMatch
  }
}

UserRepository.java

package repository;

import com.google.common.base.Preconditions;
import model.User;
import org.mindrot.jbcrypt.BCrypt;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class UserRepository {

    private final Map<String, User> repo;
    private final SessionRepository sessionRepository;
    private final String salt;

    public static final UserRepository instance = new UserRepository();

    private UserRepository() {
        this.repo = new HashMap<>();
        this.salt = BCrypt.gensalt();
        this.sessionRepository = SessionRepository.instance;
    }

    public User create(String name, String password) {
        final Optional maybeUser = Optional.ofNullable(repo.get(name));
        Preconditions.checkArgument(!maybeUser.isPresent(), "duplicated username");

        final User newUser = new User(name, hash(password));
        repo.put(name, newUser);
        return newUser;
    }

    public boolean login(String name, String password) {
        return Optional.ofNullable(repo.get(name))
                .map(u -> {
                    boolean validPassword = compare(password, u.getPassword());
                    if (validPassword) sessionRepository.login(u);
                    return validPassword;
                }).orElse(false);
    }

    public void logout(String name) {
        this.sessionRepository.logout(name);
    }

    public boolean isLoggedIn(String name) { return this.sessionRepository.isLoggedIn(name); }

    private String hash(String value) {
        return BCrypt.hashpw(value, salt);
    }

    private boolean compare(String password, String hashed) {
        return hash(password).equals(hashed);
    }

}

User repository behaves as a repository of users and as a wrapper of SessionRepository.

It defines four methods: create,login, logout and isLoggedIn

The method create receives a name and a password, and creates a user out of it and puts it in our map, but not without checking if the name is duplicated and hashing the password first.

The method login receives a name and a password, checks if a user with such name actually exists and, if it does, it compares the passwords. If they match, a session is created in SessionRepository.

The methods logout and isLoggedIn are just wrappers of the ones in SessionRepository.

Now let’s write our authentication controller.

AuthController.scala

package controllers

import play.api.data._
import play.api.data.Forms._
import play.api.mvc.{Action, Controller}
import repository.UserRepository

case class LoginForm(name: String, password: String)
case class SignUpForm(name: String, password: String, passwordConfirmation: String)

/**
  * Created by svinci on 06/12/15.
  */
class AuthController extends Controller {

  private val loginForm: Form[LoginForm] = Form(
    mapping("name" -> text, "password" -> text)(LoginForm.apply)(LoginForm.unapply)
  )
  private val signUpForm: Form[SignUpForm] = Form(
    mapping("name" -> text, "password" -> text, "passwordConfirmation" -> text)(SignUpForm.apply)(SignUpForm.unapply)
  )

  private val repository: UserRepository = UserRepository

  def login = Action {
    Ok(views.html.login(form = loginForm))
  }

  def logout = Action { implicit request =>
    repository.logout(request.session.get("user").get)
    Redirect("/login") withNewSession
  }

  def authenticate = Action { implicit request =>
    Option(loginForm.bindFromRequest().get).map(f => {
      repository.login(f.name, f.password) match {
        case true => Redirect(routes.Application.index()) withSession("user" -> f.name)
        case false => Redirect("/login")
      }
    }).getOrElse(Redirect("/login"))
  }

  def signup = Action {
    Ok(views.html.signup(signUpForm))
  }

  def newUser = Action { implicit request =>
    Option(signUpForm.bindFromRequest().get).map(f => {
      f.password == f.passwordConfirmation match {
        case true =>
          repository.create(f.name, f.password)
          Redirect("/login")
        case false => Redirect("/signup")
      }
    }).getOrElse(Redirect("/signup"))
  }

}

AuthController.java

package controllers;

import play.data.Form;
import play.mvc.Controller;
import play.mvc.Result;
import repository.UserRepository;
import views.html.login;
import views.html.signup;

import java.util.Objects;
import java.util.Optional;

public class AuthController extends Controller {

    private final UserRepository repository;

    public AuthController() {
        repository = UserRepository.instance;
    }

    public Result login() {
        return ok(login.render(Form.form(LoginForm.class)));
    }

    public Result logout() {
        repository.logout(session("user"));
        session().clear();
        return redirect(routes.AuthController.login());
    }

    public Result authenticate() {
        final Form loginForm = Form.form(LoginForm.class).bindFromRequest();
        return Optional.ofNullable(loginForm.get())
                .map(f ->  {
                    if (repository.login(f.name, f.password)) {
                        session().put("user", f.name);
                        return redirect(routes.Application.index());
                    } else return redirect(routes.AuthController.login());
                }).orElse(redirect(routes.AuthController.login()));
    }

    public Result signup() {
        return ok(signup.render(Form.form(SignUpForm.class)));
    }

    public Result newUser() {
        return Optional.ofNullable(Form.form(SignUpForm.class).bindFromRequest().get()).map(f -> {
            if (Objects.equals(f.password, f.passwordConfirmation)) {
                repository.create(f.name, f.password);
                return redirect(routes.AuthController.login());
            } else {
                return redirect(routes.AuthController.signup());
            }
        }).orElse(redirect(routes.AuthController.signup()));
    }

}

This controller defines 5 actions: login, logout, signup, newUser and authenticate:

  • login: This action renders the log in page.
  • logout: This action cleans up user’s session, deletes it from our SessionRepository and then redirects to login page.
  • signup: This action renders the create user page.
  • newUser: This action receives a SignUpForm, creates a user with its data and redirects to login page.
  • authenticate: This action receives a LoginForm and creates a session with its data.

These LoginForm and SignUpForm in scala were written in the same file, in java they must be written in separated files:

LoginForm.java

package controllers;

public class LoginForm {
    public String name;
    public String password;
}

SignUpForm.java

package controllers;

public class SignUpForm {
    public String name;
    public String password;
    public String passwordConfirmation;
}

Now, let’s move on to the templates:

index.scala.html

@(user: String)

@main(s"Welcome $user") {

    <h1>You are logged in!</h1>
    <p><a href="@routes.AuthController.logout()">Log out!</a></p>

}

The index is a simple title that says “You are logged in!, with a link to log out.

login.scala.html

@(form: Form[LoginForm])
@main("Login") {
    @helper.form(routes.AuthController.authenticate()) {
        <h1>Login</h1>
        <p>
            <input type="text" name="name" placeholder="Name" value="@form("name").value">
        </p>
        <p>
            <input type="password" name="password" placeholder="Password">
        </p>
        <p>
            <button type="submit">Login</button>
        </p>
        <p>
            Don't have a user yet? <a href="@routes.AuthController.signup()">Sign up now!</a>
        </p>
    }
}

The login page is a form that posts the LoginForm to the authenticate action and a link to the signup page.

signup.scala.html

@(form: Form[SignUpForm])
@main("Sign up!") {
    @helper.form(routes.AuthController.newUser()) {
        <h1>Sign up</h1>
        <p>
            <input type="text" name="name" placeholder="Name">
        </p>
        <p>
            <input type="password" name="password" placeholder="Password">
        </p>
        <p>
            <input type="password" name="passwordConfirmation" placeholder="Confirm Password">
        </p>
        <p>
            <button type="submit">Sign up</button>
        </p>
        <p>
            Already have a user? <a href="@routes.AuthController.login()">Log in now!</a>
        </p>
    }
}

The signup page is a form that submits de SignUpForm to the newUser action and a link to the login page.

Now, with all this done there is only one thing missing: we need to check if the user is logged in to prevent unauthenticated users to access our index page. So, in a package name controllers.action we are going to use action composition to make that check.

Secured.scala

package controllers.action

import play.api.mvc._
import repository.UserRepository

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

case class Secured[A](action: Action[A]) extends Action[A] {

  private val repository: UserRepository = UserRepository

  override def apply(request: Request[A]): Future[Result] = request.session.get("user").map(repository.isLoggedIn) match {
    case None => Future(Results.Redirect("/login"))
    case Some(r) if r => action(request)
    case Some(r) if !r => Future(Results.Redirect("/login"))
  }

  override def parser: BodyParser[A] = action.parser
}

The Scala way of doing this (one of many actually) is writing an Action wrapper that, on its apply method, makes the check and if everything is correct continues with its underlying action.

Secured.java

package controllers.action;

import org.apache.commons.lang3.StringUtils;
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Security;
import repository.UserRepository;

public class Secured extends Security.Authenticator {

    private final UserRepository repository;

    public Secured() {
        repository = UserRepository.instance;
    }

    @Override
    public String getUsername(Http.Context context) {
        final String userName = context.session().get("user");
        if (StringUtils.isNotBlank(userName) && repository.isLoggedIn(userName)) {
            return userName;
        } else {
            return null;
        }
    }

    @Override
    public Result onUnauthorized(Http.Context context) {
        return redirect("/login");
    }
}

In the java way, we extend from Security.Athenticator and make the check in the getUsername method, which is expected to return the user name if the user is logged in and null if not, and then in the onUnauthorized method we write the redirect.

Now we modify Application to use this Action to make it unavailable to any user that is not authenticated.

Application.scala

package controllers
import controllers.action.Secured
import play.api.mvc._
class Application extends Controller {
  def index = Secured {
    Action { request =>
      Ok(views.html.index(request.session.get("user").get))
    }
  }
}

Application.java

package controllers;
import controllers.action.Secured;
import play.*;
import play.mvc.*;
import views.html.*;
public class Application extends Controller {
    @Security.Authenticated(Secured.class)
    public Result index() {
        return ok(index.render(session("user")));
    }
}

And now we connect everything in our routes file:

routes

# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET     /                           controllers.Application.index()
GET     /signup                     controllers.AuthController.signup()
POST    /signup                     controllers.AuthController.newUser()
GET     /login                      controllers.AuthController.login()
POST    /login                      controllers.AuthController.authenticate()
GET     /logout                     controllers.AuthController.logout()

# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)

Now we run the application with activator run and visit http://localhost:9000/ in our favourite browser. We’ll be redirected to http://localhost:9000/login and we’ll see something like:

Play Security - Login
Play Security – Login

Then we click on the sign up link to create a user and we’ll be redirected to http://localhost:9000/signup. We’ll see:

Play Security - Sign Up
Play Security – Sign Up

We create a user and after we are redirected again to the login page, we login and then we’ll finally see our index:

Play Security - Index
Play Security – Index

So, now we have an application with login! Let’s move on.

2. Security Headers

Play provides a lot of useful filters, between them there is SecurityHeadersFilter which can be used to configure some headers in the HTTP response to mitigate security issues and provide an extra level of defense for new applications.

To make this filter available we need to add filters to our dependency list, just as we added BCrypt. Now we need to add this new filter to our application, which is typically done by creating a Filters class in the root of the project like this:

Filters.scala

    import javax.inject.Inject

    import play.api.http.HttpFilters
    import play.filters.headers.SecurityHeadersFilter

    class Filters @Inject() (securityHeadersFilter: SecurityHeadersFilter) extends HttpFilters {
      def filters = Seq(securityHeadersFilter)
    }

Filters.java

import play.api.mvc.EssentialFilter;
import play.filters.headers.SecurityHeadersFilter;
import play.http.HttpFilters;

import javax.inject.Inject;

public class Filters implements HttpFilters {

    @Inject
    SecurityHeadersFilter securityHeadersFilter;

    public EssentialFilter[] filters() {
        return new EssentialFilter[] { securityHeadersFilter };
    }
}

If you don’t want to name it Filters, or you want to locate it in another package, you just need to configure it in your application.conf like: play.http.filters = "mypackage.MyFilters".

Now, these headers’ values are configured via our application.conf, let’s see:

application.conf

user.inactivity.max=7200000 #Two Hours

play.filters.headers.frameOptions="DENY" # SETS X-Frame-Options
play.filters.headers.xssProtection="1; mode=block" # SETS X-XSS-Protection
play.filters.headers.contentTypeOptions="nosniff" # SETS X-Content-Type-Options
play.filters.headers.permittedCrossDomainPolicies="master-only" # SETS X-Permitted-Cross-Domain-Policies
play.filters.headers.contentSecurityPolicy="default-src ‘self’" # SETS Content-Security-Policy

All these values are the ones that play sets by default. Every one of these headers can be disabled by configuring it as null just as: play.filters.headers.frameOptions = null.

3. CORS

CORS (Cross-Origin Resource Sharing) is a protocol that allows web applications to make requests across different domains. Play provides a filter that configures this protocol.

From the filters added to your dependency when we added the security headers, we’ll add the new CORSFilter to it:

Filters.scala

    import javax.inject.Inject

    import play.api.http.HttpFilters
    import play.filters.headers.SecurityHeadersFilter

    class Filters @Inject() (securityHeadersFilter: SecurityHeadersFilter, corsFilter: CORSFilter) extends HttpFilters {
      def filters = Seq(securityHeadersFilter, corsFilter)
    }

Filters.java

import play.api.mvc.EssentialFilter;
import play.filters.headers.SecurityHeadersFilter;
import play.http.HttpFilters;

import javax.inject.Inject;

public class Filters implements HttpFilters {

    @Inject
    SecurityHeadersFilter securityHeadersFilter;
    @Inject
    CORSFilter corsFilter;

    public EssentialFilter[] filters() {
        return new EssentialFilter[] { securityHeadersFilter, corsFilter };
    }
}

And now we’ll configure it in our application.conf:

...
play.filters.cors {
  pathPrefixes = ["/some/path", ...]
  allowedOrigins = ["http://www.example.com", ...]
  allowedHttpMethods = ["GET", "POST"]
  allowedHttpHeaders = ["Accept"]
  preflightMaxAge = 3 days
}

And just like that, CORS is configured in our application!

4. Download the Code Project

This was an example of security in a play application.

Download
You can download the full source code of this example here: play-scala-example, play-java-example

Sebastian Vinci

Sebastian is a full stack programmer, who has strong experience in Java and Scala enterprise web applications. He is currently studying Computers Science in UBA (University of Buenos Aires) and working a full time job at a .com company as a Semi-Senior developer, involving architectural design, implementation and monitoring. He also worked in automating processes (such as data base backups, building, deploying and monitoring applications).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Ali
Ali
4 years ago

Hello,
Thank you so much for this exemple project, as a beginner i really wished that your project can turn minimally on a database with ( insertion of name + password )

Back to top button