Yes, Silhouette allows you to implement multiple authenticators . Here you can implement the JWTAuthenticator , which provides its JWT authentication service along with your CookieAuthenticator :
- As Douglas Liu already pointed out in a comment, you will need to create an additional environment type. He must connect
Identity to the appropriate Authenticator .
For instance:
trait CookieEnv extends Env { type I = Account type A = CookieAuthenticator } trait JWTEnv extends Env { type I = Account type A = JWTAuthenticator }
- Implement JWT bindings in your Silhouette module. Please take a look at play-silhouette-angular-seed for a complete 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
JWTAuthenticator configuration options in silhouette.conf :
For instance:
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 through the JWT:
For example, in your app.routes file add the following line:
- Finally, in the
AuthController add the appropriate authenticate method.
Sample 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")))) } }