Providers

In Silhouette a provider is a service that handles the authentication of an identity. It typically reads authorization information and returns information about an identity.

Request providers

Request providers are special types of providers. They can be hooked directly into incoming requests which get scanned for credentials and either gain or restrict access to the protected resources. This can be useful for machine authentication.

When using a request provider, you should consider using the dummy authenticator, because it doesn't have any network throughput or memory footprint compared to other authenticators.

Writing your own request provider

To create your own request provider to filter requests for an environment, you first need to extend the RequestProvider and implement the authenticate method. Here is an example to search for an X-API-KEY header in the request. You can also use the BasicAuthProvider for a more complex example.

class APIKeyRequestProvider extends RequestProvider with Logger {
  def authenticate[B](request: Request[B]): Future[Option[LoginInfo]] = {
    Future.successful(request.headers.get("X-API-KEY") map { x =>
      LoginInfo(id, x)
    })
  }

  override def id = "api-key"
}

Your authenticate method has to return a potential LoginInfo that the handler will then ask the IdentityService (like a UserService) to retrieve the user related to that LoginInfo. If it finds an identity related to that LoginInfo, then it will authenticate the user. If it doesn't find anything, it should return None.

Last step is to hook it up to your environment with DI (preferably using the [dummy authenticator]). Here is an example using Guice:

@Provides
  def provideAPIKeyEnvironment(
      userService: UserService,
      authenticatorService: AuthenticatorService[DummyAuthenticator],
      apiKeyRequestProvider: APIKeyRequestProvider,
      eventBus: EventBus
  ): Environment[APIKeyEnv] = {
    Environment[APIKeyEnv](
      userService,
      authenticatorService,
      Seq(apiKeyRequestProvider),
      eventBus
    )
  }

Now every request to a controller in this Environment will get scanned for a X-API-KEY header and authenticate the user if it can find an identity with that X-API-KEY!

Credentials provider

Silhouette supports local authentication, typically via an HTML form, with the credentials provider. This provider accepts credentials and returns the login information for an identity after a successful authentication. Typically credentials consist of an identifier (a username or an email address) and a password.

The credentials provider supports changing the password hashing algorithm on the fly. Sometimes it may be possible to change the hashing algorithm used by the application. But the hashes stored in the backing store can’t be converted back into plain text passwords to hash them again with the new algorithm. So if a user successfully authenticates after the application has changed the hashing algorithm, the provider hashes the entered password again with the new algorithm and stores the authentication info in the backing store.

Basic Authentication provider

Silhouette supports basic access authentication as described in RFC 2617. This provider is an implementation of a request provider which accepts the current request and returns the login information for an identity after a successful authentication.

The basic authentication provider supports changing the password hashing algorithm on the fly. Sometimes it may be possible to change the hashing algorithm used by the application. But the hashes stored in the backing store can’t be converted back into plain text passwords to hash them again with the new algorithm. So if a user successfully authenticates after the application has changed the hashing algorithm, the provider hashes the entered password again with the new algorithm and stores the authentication info in the backing store.

Social providers

A social provider allows a user to authenticate an identity on your website with an existing account from an external social website like Facebook, Google or Twitter. You can find a list of all supported providers grouped by authentication protocol here:

OAuth1

  • LinkedInProvider (www.linkedin.com)
  • TwitterProvider (www.twitter.com)
  • XingProvider (www.xing.com)

OAuth2

  • Auth0Provider (auth0.com)
  • DropboxProvider (www.dropbox.com)
  • FacebookProvider (www.facebook.com)
  • FoursquareProvider (www.foursquare.com)
  • GitHubProvider (www.github.com)
  • GitlabProvider (www.gitlab.com)
  • GoogleProvider (www.google.com)
  • InstagramProvider (www.instagram.com)
  • LinkedInProvider (www.linkedin.com)
  • VKProvider (www.vk.com)

OpenID

  • SteamProvider (www.steamcommunity.com)
  • YahooProvider (www.yahoo.com)

Social profile

The social profile contains the profile data returned from the social providers. Silhouette provides a default social profile called CommonSocialProfile, which contains the most common profile data providers return.

Social profile registry

The social profile registry can be used to inject a group of social providers into a controller instead of injecting every provider separately. This registry provides also methods to retrieve either a single provider by its ID or to retrieve a provider by its type.

Some providers return a superset of the information in CommonSocialProfile, for example the location or gender of the user. Silhouette's “profile builders” allow you to construct custom profiles without duplicating existing code. Instead of overriding the provider to return the additional profile information, developers can mix in a profile builder which adds only the programming logic for the additional fields.

Every profile builder must define a profile parser, which transforms the content returned
from the provider into a social profile instance. Parsers can then be reused by other
parsers to avoid code duplication.

Write a custom social profile builder

As noted above, it is very easy to write your own profile builder implementations. Let's take a look on the following code examples. The first one defines a custom social profile that differs from the common social profile by the additional gender field.

case class CustomSocialProfile(
  loginInfo: LoginInfo,
  firstName: Option[String] = None,
  lastName: Option[String] = None,
  fullName: Option[String] = None,
  email: Option[String] = None,
  avatarURL: Option[String] = None,
  gender: Option[String] = None) extends SocialProfile

Next, we create the parser which uses the default Facebook profile parser to avoid code duplication.

class CustomFacebookProfileParser 
  extends SocialProfileParser[JsValue, CustomSocialProfile,  OAuth2Info] {

  /**
   * The common social profile parser.
   */
  val commonParser = new FacebookProfileParser

  /**
   * Parses the social profile.
   *
   * @param json The content returned from the provider.
   * @return The social profile from given result.
   */
  def parse(json: JsValue, authInfo: OAuth2Info) = {
    commonParser.parse(json, authInfo).map { commonProfile =>
      val gender = (json \ "gender").as[String]
      CustomSocialProfile(
        loginInfo = commonProfile.loginInfo,
        firstName = commonProfile.firstName,
        lastName = commonProfile.lastName,
        fullName = commonProfile.fullName,
        avatarURL = commonProfile.avatarURL,
        email = commonProfile.email,
        gender = Some(gender))
    }                                        
  }
}

As you can see, there is no need to duplicate any Json parsing. The only thing to do is to query the gender field from the Json response returned by the Facebook API.

Lastly, we create a new provider instance which ties the pieces together.

class CustomFacebookProvider(
  protected val httpLayer: HTTPLayer,
  protected val stateProvider: OAuth2StateProvider,
  val settings: OAuth2Settings)
  extends BaseFacebookProvider {

  /**
   * The type of this class.
   */
  type Self = CustomFacebookProvider

  /**
   * The type of the profile a profile builder is responsible for.
   */
  type Profile = CustomSocialProfile

  /**
   * The profile parser.
   */
  val profileParser = new CustomFacebookProfileParser

  /**
   * Gets a provider initialized with a new settings object.
   *
   * @param f A function which gets the settings passed and returns different settings.
   * @return An instance of the provider initialized with new settings.
   */
  def withSettings(f: (Settings) => Settings) = {
    new CustomFacebookProvider(httpLayer, stateProvider, f(settings))
  }
}

Now you can instantiate your new created provider.

new CustomFacebookProvider(httpLayer, stateProvider, settings)

OAuth1 token secret

During a typical OAuth1 flow, a provider retrieves a request token in conjunction with a token secret. This token secret is then needed to create the signature for retrieving the access token from the OAuth1 provider. Due to the fact that we leave our application during the OAuth flow to authorize against the provider, we must cache the token secret so that is available to obtain the access token after redirecting back to our application.

To maintain the token secret in Silhouette, a token secret provider must be passed to every OAuth1 authentication provider. All token secret provider implementations can be found in the secrets package.

List of OAuth1 token secret providers

We provide some built-in token secret providers.

The cookie secret works by embedding an encrypted token secret in a cookie. This provides a stateless/scalable approach.

👍

Tip

Please take a look on the configuration settings, on how to configure the provider for this secret.

TOTP provider

Silhouette supports TOTP (Time-based One-time Password) authentication, including scratch codes verification.

Recipe of usage:

  1. Call createCredentials method to start syncing user data with TOTP mechanism. It returns TOTP data including scratch codes and QR-url for user authenticator app.
  2. Call authenticate method to check whether user can be authenticated by verification code or scratch code

Scratch codes stored like a PasswordInfo, so it require PasswordHasher to create scratch code's hash.

Existing implementations:

GoogleTotpProvider

TOTP implementation based on the OpenSource solution . With this implementation any compatible solutions (andOTP, FreeOTP, authy) can be used to generate OTP tokens.

Request extractors

The default workflow for traditional web applications is it to send values in URL query
parameters, but for mobile applications there could be another workflow. With request
extractors it's possible to extract values sent to the client in different parts of the request. By default Silhouette can read values from query parameters and from a request body containing form-urlencoded, Json or XML data.

For example, if a parameter with the name code is needed by Silhouette inside a provider, then the parameter could be sent in the following parts of the request:

?code=value
code=value
{"code": "value"}
value

📘

Note

Parameters sent as query parameters always have precedence over parameters sent in the body of a request. So if a parameter is sent in query and in body, then the query parameter wins.

Define custom request extractors

It is possible to define custom request extractors by providing an implicit RequestExtractor implementation.

The AuthInfo implementation contains authentication information such as access tokens, hashed passwords, and so on -- which should never be exposed to the public. Each provider defines its own AuthInfo implementation.

As with other Silhouette structures that vary in their implementation, AuthInfo is managed by an AuthInfoRepository that find, add, update or remove the information as needed.

Updated about a year ago