I used the play-silhouette-seed as a template for my application. So in my project, I use a cookie based authenticator (CookieAuthenticator
). This works absolutely fine, even for REST calls via JavaScript which is embedded in my Twirl template. However, now I want to make also REST calls programmatically in clients other than a browser. As a consequence, I would have to retrieve the Set-Cookie: authenticator=...
element on each response and set it as part of my request. In my JavaScript snippet which is embedded in my Twirl template and rendered in the browser, this is no problem because I don't have to deal with that, but for other clients (server etc.) this causes headaches.
I want to implement now a JWTAuthenticator
in addition to my CookieAuthenticator
. Is this even supported, or do I have to switch completely to JWTAuthenticator
? Furthermore, do I need separate actions, even though everything should be the same implementation except the authenticator?
Yes, Silhouette allows you to implement multiple authenticators. Here's how you can implement the JWTAuthenticator
that provides its JWT authenticator service along with your CookieAuthenticator
:
Identity
with the respective Authenticator
. For example:
trait CookieEnv extends Env {
type I = Account
type A = CookieAuthenticator
}
trait JWTEnv extends Env {
type I = Account
type A = JWTAuthenticator
}
For instance:
class SilhouetteModule extends AbstractModule with ScalaModule {
def configure() {
bind[Silhouette[CookieEnv]].to[SilhouetteProvider[CookieEnv]]
bind[Silhouette[JWTEnv]].to[SilhouetteProvider[JWTEnv]]
// ...
()
}
@Provides
def provideCookieEnvironment(
userService: AccountService,
authenticatorService: AuthenticatorService[CookieAuthenticator],
eventBus: EventBus): Environment[CookieEnv] = {
Environment[CookieEnv](
userService,
authenticatorService,
Seq(),
eventBus
)
}
@Provides
def provideJWTEnvironment(
userService: AccountService,
authenticatorService: AuthenticatorService[JWTAuthenticator],
eventBus: EventBus): Environment[JWTEnv] = {
Environment[JWTEnv](
userService,
authenticatorService,
Seq(),
eventBus
)
}
// ...
@Provides
def provideCookieAuthenticatorService(
@Named("authenticator-cookie-signer") cookieSigner: CookieSigner,
@Named("authenticator-crypter") crypter: Crypter,
fingerprintGenerator: FingerprintGenerator,
idGenerator: IDGenerator,
configuration: Configuration,
clock: Clock): AuthenticatorService[CookieAuthenticator] = {
val config = configuration.underlying.as[CookieAuthenticatorSettings]("silhouette.authenticator")
val encoder = new CrypterAuthenticatorEncoder(crypter)
new CookieAuthenticatorService(config, None, cookieSigner, encoder, fingerprintGenerator, idGenerator, clock)
}
@Provides
def provideJWTAuthenticatorService(
@Named("authenticator-crypter") crypter: Crypter,
idGenerator: IDGenerator,
configuration: Configuration,
clock: Clock): AuthenticatorService[JWTAuthenticator] = {
val config = configuration.underlying.as[JWTAuthenticatorSettings]("silhouette.authenticator")
val encoder = new CrypterAuthenticatorEncoder(crypter)
new JWTAuthenticatorService(config, None, encoder, idGenerator, clock)
}
// ...
}
JWTAuthenticator
configuration settings to your silhouette.conf
:For example:
authenticator.fieldName = "X-Auth-Token"
authenticator.requestParts = ["headers"]
authenticator.issuerClaim = "Your fancy app"
authenticator.authenticatorExpiry = 12 hours
authenticator.sharedSecret = "!!!changeme!!!"
For instance, in your app.routes
file, add the following line:
# JWT Authentication
POST /api/jwt/authenticate controllers.auth.api.AuthController.authenticate
AuthController
, add the corresponding authenticate
method.Example code (adapted from SignInController.scala
):
implicit val dataReads = (
(__ \ 'email).read[String] and
(__ \ 'password).read[String] and
(__ \ 'rememberMe).read[Boolean]
) (SignInForm.SignInData.apply _)
def authenticate = Action.async(parse.json) { implicit request =>
request.body.validate[SignInForm.SignInData].map { signInData =>
credentialsProvider.authenticate(Credentials(signInData.email, signInData.password)).flatMap { loginInfo =>
accountService.retrieve(loginInfo).flatMap {
case Some(user) => silhouette.env.authenticatorService.create(loginInfo).map {
case authenticator if signInData.rememberMe =>
val c = configuration.underlying
authenticator.copy(
expirationDateTime = clock.now + c.as[FiniteDuration]("silhouette.authenticator.rememberMe.authenticatorExpiry"),
idleTimeout = c.getAs[FiniteDuration]("silhouette.authenticator.rememberMe.authenticatorIdleTimeout")
)
case authenticator => authenticator
}.flatMap { authenticator =>
Logger.info(s"User ${user._id} successfully authenticated.")
silhouette.env.eventBus.publish(LoginEvent(user, request))
silhouette.env.authenticatorService.init(authenticator).map { token =>
Ok(Json.obj("token" -> token))
}
}
case None => Future.failed(new IdentityNotFoundException("Couldn't find user."))
}
}.recover {
/* Login did not succeed, because user provided invalid credentials. */
case e: ProviderException =>
Logger.info(s"Host ${request.remoteAddress} tried to login with invalid credentials (email: ${signInData.email}).")
Unauthorized(Json.obj("error" -> Messages("error.invalidCredentials")))
}
}.recoverTotal {
case e: JsError =>
Logger.info(s"Host ${request.remoteAddress} sent invalid auth payload. Error: $e.")
Future.successful(Unauthorized(Json.obj("error" -> Messages("error.invalidPayload"))))
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With