Playing with authentication

A frequent piece of functionality needed when designing a new web application involves authentication and authorization. Authentication usually requires that the user provide the credentials to log in to the application in the form of a username/password. Authorization is the mechanism by which the system can ensure that a user can perform only the operations that he/she is entitled to. In this section, we are going to extend our helloworld sample with security features that are part of the Play distribution, as a way to demonstrate how the usage of traits in Scala can provide an elegant solution to conventional problems.

Let's define a new controller that we will call Authentication, which contains common methods such as login to retrieve a sign-in page, authenticate and check to perform the verification of the authentication, and logout to go back to the login page. This is done as follows:

object Authentication extends Controller {

  val loginForm = Form(
    tuple(
      "email" -> text,
      "password" -> text
    ) verifying ("Invalid email or password", result => result match {
      case (email, password) => check(email, password)
    })
  )

  def check(username: String, password: String) = {
    (username == "thomas@home" && password == "1234")  
  }

  def login = Action { implicit request =>
    Ok(html.login(loginForm))
  }

  def authenticate = Action { implicit request =>
    loginForm.bindFromRequest.fold(
      formWithErrors => BadRequest(html.login(formWithErrors)),
      user => Redirect(routes.Application.index).withSession(Security.username -> user._1)
    )
  }

  def logout = Action {
    Redirect(routes.Authentication.login).withNewSession.flashing(
      "success" -> "You are now logged out."
    )
  }
}

Similar to the index method that belongs to the Application controller from the previous section, the login method here consists of binding a form (named loginForm) to a view (named html.login, corresponding to the file views/login.scala.html). A simple template for a view that consists of two text fields to capture an e-mail/username and password is shown as follows:

@(form: Form[(String,String)])(implicit flash: Flash)

@main("Sign in") {
        
        @helper.form(routes.Authentication.authenticate) {
            
            @form.globalError.map { error =>
                <p class="error">
                    @error.message
                </p>
            }
            
            @flash.get("success").map { message =>
                <p class="success">
                    @message
                </p>
            }
            
            <p>
                <input type="email" name="email" placeholder="Email" id="email" value="@form("email").value">
            </p>
            <p>
                <input type="password" name="password" id="password" placeholder="Password">
            </p>
            <p>
                <button type="submit" id="loginbutton">Login</button>
            </p>
            
        }
        
        <p class="note">
            Try login as <em>thomas@@home</em> with <em>1234</em> as password.
        </p>
            
}

Notice how the thomas@@home username shows us that you can escape the special @ character by entering it twice.

Now we have the logic to handle an HTML login page with the submission of the credentials to be authenticated, but we are still lacking the missing piece that will wrap a conventional invocation of a method from any controller that we want to protect. Moreover, this logic will redirect us to the login page in case the username (a property stored in our request.session object and retrieved from a cookie) is not present. It can be described in a trait as follows:

trait Secured {

  def username(request: RequestHeader) = request.session.get(Security.username)

  def onUnauthorized(request: RequestHeader) = Results.Redirect(routes.Authentication.login)

  def withAuth(f: => String => Request[AnyContent] => SimpleResult) = {
    Security.Authenticated(username, onUnauthorized) { user =>
      Action(request => f(user)(request))
    }
  }
}

We can add this trait to the same Authentication.scala controller class. The withAuth method wraps our Action invocations by applying the Security.Authenticated method around them. To be able to use this trait, we just need to mix it in in our controller class as follows:

object Application extends Controller with Secured {
  …
}

Once the trait is part of our controller, we can replace an Action method with a withAuth method instead. For example, when invoking the index method, we replace the Action method, as follows:

/**
 * Home page
 */
def index = withAuth { username => implicit request =>
  Ok(html.index(helloForm))
}

To be able to execute our new functionality, we should not forget to add the extra methods from the Authentication.scala controller to the routes' definitions (the compiler will flag this if we omit them):

# Authentication
GET    /login    controllers.Authentication.login
POST   /login    controllers.Authentication.authenticate
GET    /logout   controllers.Authentication.logout

Let's rerun the application and invoke the http://localhost:9000/ page. We should be routed to the login.html page rather than the index.html page. This is shown in the following screenshot:

Playing with authentication

Try to log in with both the erroneous and correct e-mail/password combinations to verify that the authentication has been implemented correctly.

This basic authentication mechanism is just an example of how you can easily extend the applications in Play. It demonstrates the use of the Action composition, a technique that can also be applied to many other aspects—for example, logging or modifying requests—and is a good alternative to interceptors.

There are, of course, external modules that you can use with Play if you need to achieve authentication through other services; for instance, modules based on standards such as OAuth, OAuth2, or OpenID. The SecureSocial module is a good example to do this and is available at http://securesocial.ws.

..................Content has been hidden....................

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