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:
- We use
@provides
for the method that builds the environment - 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.
- We created both an abstract
UserService
trait, and an implementation, ConfigUserServiceImpl - 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.
- The request authenticator is passed in the environment as
Seq(requestProvider)
and dinamically injected with
bind[RequestProvider].to[BasicAuthProvider].asEagerSingleton()
- 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]
- 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
Posted on August 18, 2018 by Roberto Congiu
0