GoodSoft
  • Projects
  • Our team
  • Clients
  • Technologies
  • Careers
  • Blog
  • Contact
  • Hub of Talents
  • eu
  • English
    • Polski
    • English
    • Deutsch
Select Page

Play Silhouette and Social logins

by GoodSoft | Jun 2, 2022 | Web | 0 comments

Google Facebook Linkedin login implementation programming play silhouette

  Play Silhouette and Social logins

Most users can’t be bothered to fill in a form with their email, fullname, same login as on every other website, and (the hardest part) two identical, complicated and strong passwords. Then every time they come back to you’re site, they have to login, which means reminding themselves which password they have used. One way of simplifying this is by authorizing users to your app by using social logins.

Play and Silhouette

Silhouette is a popular Scala authentication library for the Play Framework. It supports multiple authentication methods – including OAuth2. It also includes multiple pre configured Provider classes for popular social sites, which minimizes the amount of necessary work.

Configuration

First thing we need to configure is the state parameter provider. We will use it to prevent CSRF attacks.

First step is adding some config keys and values to silhouette.conf. Our snippet below presents configuration for both the CSRF state handler and the OAuth2 state provider – since we will be needing both of them.

# OAuth2 state provider settings
oauth2StateProvider.cookieName="OAuth2TokenSecret"
oauth2StateProvider.cookiePath="/"
oauth2StateProvider.secureCookie=false
oauth2StateProvider.httpOnlyCookie=true
oauth2StateProvider.sameSite="Lax"
oauth2StateProvider.expirationTime=5 minutes
oauth2StateProvider.signer.key = "[changeme]"
oauth2StateProvider.crypter.key = "[changeme]"

# Social state handler
socialStateHandler.signer.key = "[changeme]"

# CSRF state item handler settings
csrfStateItemHandler.cookieName="OAuth2State"
csrfStateItemHandler.cookiePath="/"
csrfStateItemHandler.secureCookie=false
csrfStateItemHandler.httpOnlyCookie=true
csrfStateItemHandler.sameSite="Lax"
csrfStateItemHandler.expirationTime=5 minutes

csrfStateItemHandler.signer.key = "[changeme]"

Signers

Next thing that needs to be done is creating Signers for the CSRF state item handler and OAuth2 state provider. They will be used to sign client side cookies. This implementation should look like the one in below and it should be placed in the same file as rest of Silhouette’s functions e.g. SilhouetteModule.scala.

/**
 * Provides the signer for the social state handler.
 *
 * @param configuration The Play configuration.
 * @return The signer for the social state handler.
 */
@Provides @Named("social-state-signer")
def provideSocialStateSigner(configuration: Configuration): Signer = {
 val config = configuration.underlying.as[JcaSignerSettings]("silhouette.socialStateHandler.signer")

 new JcaSigner(config)
}
/**
 * Provides the signer for the CSRF state item handler.
 *
 * @param configuration The Play configuration.
 * @return The signer for the CSRF state item handler.
 */
@Provides @Named("csrf-state-item-signer")
def provideCSRFStateItemSigner(configuration: Configuration): Signer = {
 val config = configuration.underlying.as[JcaSignerSettings]("silhouette.csrfStateItemHandler.signer")

 new JcaSigner(config)
}

Providers

After creating Signers, we can create both a CSRF state item handler and social state handler. First we will create a CSRF item handler.

/**
 * Provides the CSRF state item handler.
 *
 * @param idGenerator The ID generator implementation.
 * @param signer The signer implementation.
 * @param configuration The Play configuration.
 * @return The CSRF state item implementation.
 */
@Provides
def provideCsrfStateItemHandler(
 idGenerator: IDGenerator,
 @Named("csrf-state-item-signer") signer: Signer,
 configuration: Configuration): CsrfStateItemHandler = {
 val settings = configuration.underlying.as[CsrfStateSettings]("silhouette.csrfStateItemHandler")
 new CsrfStateItemHandler(settings, idGenerator, signer)
}

Now using this CSRF state item handler we will create the social state handler. We will use DefaultSocialStateHandler as Silhouette documentation suggests that it’s the best option.

/**
 * Provides the social state handler.
 *
 * @param signer The signer implementation.
 * @return The social state handler implementation.
 */
@Provides
def provideSocialStateHandler(
 @Named("social-state-signer") signer: Signer,
 csrfStateItemHandler: CsrfStateItemHandler): SocialStateHandler = {

 new DefaultSocialStateHandler(Set(csrfStateItemHandler), signer)
}

Social media providers

Now that we have configured Silhouette for OAuth2 workflow and secured it against CSRF attacks we can create our social site providers. For the purpose of this article we will implement only Google provider, but other configurations are very similar. You can find sample configurations in the Silhouette documentation. Mostly likely you will want to use more than one provider for your users comfort. Code presented in this part of the article will be easily extensible.

Provider configuration

First thing that needs to be done is adding provider’s configuration to silhouette.conf file. For Google it should look similar to the one below.

google {
     authorizationURL="https://accounts.google.com/o/oauth2/auth"
     accessTokenURL="https://accounts.google.com/o/oauth2/token"
     redirectURL=YOUR_APP_DOMAIN/authenticate/google"
     clientID="some_client_id"
     clientSecret="some_client_secret"
     scope="profile email"
   }

Note: You need to generate clientID and clientSecret in your Google account.

Next step is to create a GoogleProvider. Again we will do this in SilhouetteModule class. Code snippet below presents an implementation that will use the configuration stored in silhouette.conf.

/**
 * Provides the Google provider.
 *
 * @param httpLayer The HTTP layer implementation.
 * @param socialStateHandler The social state handler implementation.
 * @param configuration The Play configuration.
 * @return The Google provider.
 */
@Provides
def provideGoogleProvider(
 httpLayer: HTTPLayer,
 socialStateHandler: SocialStateHandler,
 configuration: Configuration): GoogleProvider = {

 new GoogleProvider(httpLayer, socialStateHandler, configuration.underlying.as[OAuth2Settings]("silhouette.google"))
}

Now we could use just this provider in for further implementation, but for ease of extensibility we will create SocialProviderRegistry. This will allow us to easily add more providers in future without the necessity to change the implementation of other classes. We can create this registry like this:

/**
 * Provides the social provider registry.
 *
 * @param facebookProvider The Facebook provider implementation.
 * @param googleProvider The Google provider implementation.
 * @param linkedInProvider The LinkedIn provider implementation.
 * @return The Silhouette environment.
 */
@Provides
def provideSocialProviderRegistry(
 googleProvider: GoogleProvider
 // add more providers here
 ): SocialProviderRegistry = {

 SocialProviderRegistry(Seq(
   googleProvider
   // add more providers here
 )) }

Authentication Controller

Now that we have providers created we need to create an endpoint for authentication. First, to access our repository, we need to inject it to the controller, by adding @Inject()(socialProviderRegistry: SocialProviderRegistry) to controller’s class. Next step is to create Action that will sign in users, based on data sent by the provider. Method below takes the provider string to determine which social provider was used by our user and use the correct one. Next it will try to authenticate the user based on using the previously determined provider. If it ends in success (Right) it will retrieve profile data sent by the social provider and will save it to the database (userService.save(profile) is a custom function that you have to create by yourself). If service returns Some(user) which in our implementation means that data was correctly saved, it will create an authenticator and publish a LoginEvent effectively logging in a new user.

/**
 * Social log in
 *
 * @param provider login provider
 */
def socialAuthenticate(provider: String) = silhouette.UnsecuredAction.async { implicit request =>
 (socialProviderRegistry.get[SocialProvider](provider) match {

   case Some(p: SocialProvider with CommonSocialProfileBuilder) =>
     p.authenticate().flatMap {

       case Left(result) => Future.successful(result) // Return authentication result with error

       case Right(authInfo) => (for {
         profile <- p.retrieveProfile(authInfo)
         optionalUser <- userService.save(profile)        } yield optionalUser match {          case Some(user) =>
           for {
             authenticator <- silhouette.env.authenticatorService.create(user.loginInfo)
             value <- silhouette.env.authenticatorService.init(authenticator)
             result <- silhouette.env.authenticatorService.embed(value, Redirect(routes.HomeController.index()))            } yield {              silhouette.env.eventBus.publish(LoginEvent(user, request))              result            }          case None => // Error handling while saving user
           Future.successful(Redirect(routes.AuthController.login).flashing("warning" -> Messages("auth.social.alreadyRegistered")))

       }).flatten

     }

   case _ => Future.failed(new ProviderException(s"Cannot authenticate with unexpected social provider $provider")) // Wrong provider key

 }).recover { // Any other unforeseen error handling
   case e: ProviderException =>
     logger.error("Unexpected provider error", e)
     Redirect(routes.AuthController.login).flashing("warning" -> Messages("auth.social.couldNotAuthenticate"))
 }
}

As with every endpoint we need to add it to the routes configuration. It should look similar to this:

GET   /authenticate/:provider  controllers.AuthController.socialAuthenticate(provider: String)

Template

One last thing that needs to be done is allowing users to access this sign in method. We will do this by adding this snippet to the sign in template.

@if(socialProviders.providers.nonEmpty) {
  <div class="social-providers mt-2">
     <p class="col-sm-12 justify-content-center d-flex">@messages("auth.signIn.useSocial")</p>
     <div class="col-sm-12 justify-content-center d-flex">
        @for(p <- socialProviders.providers) {       
           <a href="@controllers.routes.AuthController.socialAuthenticate(p.id)" class="social-auth provider @p.id" title="@messages(p.id)"><i aria-hidden="true" class="fab fa-@{p.id} font-50 m-2"></i></a>
        }
     </div>
  </div>
}

This will create links to all social providers from our Repository. class="fab fa-@{p.id} font-50 m-2" will fetch provider icons from FontAwesome.

Poland, Białystok ul. Żurawia 71

+48 512 098 660

contact@goodsoft.pl

  • Follow
  • Follow

Project covered

GoodSoft company is implementing a project co-financed by the European Funds from the Operational Programme Eastern Poland Priority axis 1 Entrepreneurial Eastern Poland measure 1.2 Internationalization of SMEs entitled ” Internationalization as an opportunity for the development of GoodSoft Michał Jurczuk company”, project number POPW .01.02.00-20-0026/20

Privacy policy

This website uses cookies for profiling purposes. Please read our cookie policy and agree to the actions taken. I accept cookies.

See more

Projects
Our Team
Clients
Technologies
Careers
Blog
Contact

Goodsoft | ul. Żurawia 71, 15-540 Białystok, Polska | NIP: 603 002 63 26 | Regon: 200 66 70 57