Basic Authorization and htaccess style authentication on the Play! Framework an Silhouete

Posted on August 18, 2018 by

0


Silhouette is probably the best library to implement authentication and authorization within the Play Framework.

Git repo here : https://github.com/rcongiu/play-silhouette-basic-auth

It is very powerful, as you can manage a common identity from multiple providers, so you can have users logging into your site from google, facebook, JWT,  and may other methods.

It also allows you to fine tune both authentication – that a user has valid credentials – and authorizaton – that after being authentication, that user also has the right permissions to access a particular resource.

Its API is very elegant, as you can just change the type of your controller Action to SecuredAction.

It is however a pretty sizable framework, and it can be daunting as a beginner.  It needs you to set up identities, often on a database, and you have to build your user credentials management.

Sometimes however you may just want a very simple authentication, for example, when prototyping or writing a Proof of Concept (POC). You also may be tasked to replace an application running on Apache or NGINX that uses a htpasswd password file.

I looked around to find an example for implementing Basic Authentication with play and I was pretty surprised that I couldn’t quite find one.
Let me be clear here, Basic Authentication is not the best idea for security, but sometimes you just need something that does the job of protecting your app with a password, but you don’t have the time to deal with full blown user management.
As a side note, if you’re working with containers, you could use a nginx reverse proxy to manage authentication, but sometimes you can’t do that. In my case, the play application had specific logic to execute on authentication failure, so I couldn’t just delegate it to nginx.
Or as I just said, you may just want to be backward compatible with an older app that uses an htpasswd file.

In this case, you can use the code here as a template.

Setting up the Dependency Injection and the Environment

Silhouette relies heavily on DI to configure its objects. First of all you have to configure an Environment, where you declare what’s yours user model and what it’s going to authenticate the user.

From silhouette’s documentation, an environment may look like:

trait SessionEnv extends Env {
  type I = User
  type A = SessionAuthenticator
}

We need our User definition – it depends on your case, he User class will hold all the relevant User information you want to be passed to the controller.
In this example, I will keep it minimal and will only keep the username there. In more complex cases you may want the date of last login, the email, name, etc.

Our definition is again the simplest, this is our utils/auth/User.scala:

package utils.auth

import com.mohiva.play.silhouette.api.Identity

/**
  * Model for your users. In Basic Auth, you may only have a username,
  * so we keep it simple and we just have the username here.
  * @param username
  */
case class User(username: String) extends Identity

And in the Module file, where we define all the DI and build the environment, we have:

import com.google.inject.{AbstractModule, Provides}
import java.time.Clock

import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository
import com.mohiva.play.silhouette.api._
import com.mohiva.play.silhouette.api.services.AuthenticatorService
import com.mohiva.play.silhouette.api.util.PasswordHasherRegistry
import com.mohiva.play.silhouette.impl.authenticators.{DummyAuthenticator, DummyAuthenticatorService}
import com.mohiva.play.silhouette.impl.providers.BasicAuthProvider
import com.mohiva.play.silhouette.password.BCryptPasswordHasher
import _root_.services._
import utils.auth._
import utils.auth.repositories.HtpasswdAuthInfoRepository
// use scala guice binding
import net.codingwell.scalaguice.{ ScalaModule, ScalaPrivateModule }
import scala.concurrent.ExecutionContext.Implicits.global

class Module extends AbstractModule with ScalaModule {

  override def configure() = {
    // Use the system clock as the default implementation of Clock
    bind[Clock].toInstance(Clock.systemDefaultZone)
    // Ask Guice to create an instance of ApplicationTimer when the
    // application starts.
    bind[ApplicationTimer].asEagerSingleton()
    // Set AtomicCounter as the implementation for Counter.
    bind[Counter].to[AtomicCounter]

    // authentication - silhouette bindings
    bind[Silhouette[DefaultEnv]].to[SilhouetteProvider[DefaultEnv]]
    bind[RequestProvider].to[BasicAuthProvider].asEagerSingleton()

    bind[UserService].to[ConfigUserServiceImpl]
    bind[PasswordHasherRegistry].toInstance(PasswordHasherRegistry(
      current = new BCryptPasswordHasher(),
      // if you want
      // current = new DummyPasswordHasher(),
      deprecated = Seq()
    ))
    bind[AuthenticatorService[DummyAuthenticator]].toInstance(new DummyAuthenticatorService)

    // configure a single username/password in play config
    //bind[AuthInfoRepository].to[ConfigAuthInfoRepository].asEagerSingleton()
    // or bind to htpasswd, set its location and its crypto hashing algorithm in config
    bind[AuthInfoRepository].to[HtpasswdAuthInfoRepository]
  }


  @Provides
  def provideEnvironment(
                          userService:          UserService,
                          authenticatorService: AuthenticatorService[DummyAuthenticator],
                          eventBus:             EventBus,
                          requestProvider:      RequestProvider
                        ): Environment[DefaultEnv] = {

    Environment[DefaultEnv](
      userService,
      authenticatorService,
      Seq(requestProvider),
      eventBus
    )
  }
}

This is actually what does most of the magic. A few comments:

  1. We use @provides for the method that builds the environment
  2. We use a simple UserService that creates the User object from the loginInfo:
    override def retrieve(loginInfo: LoginInfo): 
          Future[Option[User]] = Future { 
               Some(User(loginInfo.providerKey)) }

    As you can see, it creates a User object using the username entered.

  3. We created both an abstract UserService trait, and an implementation, ConfigUserServiceImpl
  4. We use DummyAuthenticator for Basic Authorization because the Authenticator is needed only when some state is saved between two HTTP requests (cookie,session), while in Basic Authentication every request is authenticated through a Request Authenticator.
  5. The request authenticator is passed in the environment as
    Seq(requestProvider)

    and dinamically injected with

    bind[RequestProvider].to[BasicAuthProvider].asEagerSingleton()
  6. The password could be stored anywhere. I wrote two classes to get the password from a htpasswd file or from the play config. You bind one with either
    bind[AuthInfoRepository].to[HtpasswdAuthInfoRepository]

    or

    bind[AuthInfoRepository].to[ConfigAuthInfoRepository]
  7. You also have to pick the hashers, since the password is usually stored hashed. It is done in
    bind[PasswordHasherRegistry].toInstance(PasswordHasherRegistry(
          current = new BCryptPasswordHasher(),
          // if you want
          // current = new DummyPasswordHasher(),
          deprecated = Seq()
        ))

Using htpasswd

You can use apache’s htpasswd to generate a password file to be used with these classes:

htpasswd -c -B  filename myuser

If you’re using htpasswd, you have to

bind[AuthInfoRepository].to[HtpasswdAuthInfoRepository]

and configure where htpasswd is in the play configuration setting security.htpasswd.file.

That class reads htpasswd file, retrieves the user’s hashed password and compares it to the hashed supplied password.
Note that only bcrypt is supported (no md5, crypto).

Hashing, or not hashing

Sometimes you want the password stored in cleartext, it is insecure, but it may be just a simple prototype, in that case use:

  bind[PasswordHasherRegistry].toInstance(PasswordHasherRegistry(
      current = new DummyPasswordHasher(),
      deprecated = Seq()
    ))

I can’t stress enough how insecure it is to use a cleartext password, but sometimes you may be using a combination of other systems and it may be your only choice. I list it here as a last resort kind of tool.

Hope you enjoyed this article about implementing the simplest yet quickest kind of authentication on a play app.
If you want to integrate it quickly check it out from github and just plug in the auth directory and add the bindings in Module.scala.
Git repo here : https://github.com/rcongiu/play-silhouette-basic-auth

Comments

comments

Posted in: programming