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
:
- As Douglas Liu, already pointed out in the comment, you will need to create an additional environment type. It should connect an
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
}
- Implement the JWT bindings in your Silhouette module. Please take a look at play-silhouette-angular-seed for a full example.
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)
}
// ...
}
- Add the
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!!!"
- Create a separate route for authentication via JWT:
For instance, in your app.routes
file, add the following line:
# JWT Authentication
POST /api/jwt/authenticate controllers.auth.api.AuthController.authenticate
- Finally, in your
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"))))
}
}