diff --git a/README.md b/README.md index a9fbe7c..61406de 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Silhouette Seed Template ===================================== -The Silhouette Seed project is an Activator template which shows how [Silhouette](https://github.com/mohiva/play-silhouette) can be implemented in a Play Framework application. It's a starting point which can be extended to fit your needs. +The Silhouette Seed project is an example application which shows how [Silhouette](https://github.com/mohiva/play-silhouette) can be implemented in a Play Framework application. It's a starting point which can be extended to fit your needs. ## Example @@ -23,16 +23,32 @@ Or you can find a running example of this template under the following URL: http * Password reset/change functionality * Account activation functionality * Email sending and auth token cleanup -* [Security headers](https://www.playframework.com/documentation/2.4.x/SecurityHeaders) -* [CSRF Protection](https://www.playframework.com/documentation/2.4.x/ScalaCsrf) +* [Security headers](https://www.playframework.com/documentation/latest/SecurityHeaders) +* [CSRF Protection](https://www.playframework.com/documentation/latest/ScalaCsrf) ## Documentation Consult the [Silhouette documentation](http://silhouette.mohiva.com/docs) for more information. If you need help with the integration of Silhouette into your project, don't hesitate and ask questions in our [mailing list](https://groups.google.com/forum/#!forum/play-silhouette) or on [Stack Overflow](http://stackoverflow.com/questions/tagged/playframework). -## Activator +## Social Authentication Providers -See https://typesafe.com/activator/template/play-silhouette-seed +If you are testing social authentication, you'll want to set up a Heroku test application as it's publicly available and free for developers. Play integration is [straightforward](https://devcenter.heroku.com/articles/play-support). + +### Twitter + +Create an application in [Twitter Developer Console](https://developer.twitter.com/en/apps). + +The key and secret that you should use for authentication is under "Keys and Tokens" tab in the "Consumer API keys" section. + +### Github + +Create an application in [Github Developer Console](https://github.com/settings/applications/new). + +### Google + +You will need to [set up an application](https://developers.google.com/identity/protocols/OpenIDConnect#appsetup) in Google API Console and enable the People API, then [set up authentication](https://developers.google.com/identity/protocols/OpenIDConnect#authenticatingtheuser). + +There is a good guide at [oauth.com](https://www.oauth.com/oauth2-servers/signing-in-with-google/create-an-application/). # License diff --git a/app/modules/SilhouetteModule.scala b/app/modules/SilhouetteModule.scala index 129852c..3eceb65 100644 --- a/app/modules/SilhouetteModule.scala +++ b/app/modules/SilhouetteModule.scala @@ -133,14 +133,16 @@ class SilhouetteModule extends AbstractModule with ScalaModule { def provideSocialProviderRegistry( facebookProvider: FacebookProvider, googleProvider: GoogleProvider, - vkProvider: VKProvider, + githubProvider: GitHubProvider, twitterProvider: TwitterProvider, + vkProvider: VKProvider, xingProvider: XingProvider, yahooProvider: YahooProvider): SocialProviderRegistry = { SocialProviderRegistry(Seq( - googleProvider, facebookProvider, + googleProvider, + githubProvider, twitterProvider, vkProvider, xingProvider, @@ -229,12 +231,7 @@ class SilhouetteModule extends AbstractModule with ScalaModule { /** * Provides the auth info repository. * - * @param totpInfoDAO The implementation of the delegable totp auth info DAO. - * @param passwordInfoDAO The implementation of the delegable password auth info DAO. - * @param oauth1InfoDAO The implementation of the delegable OAuth1 auth info DAO. - * @param oauth2InfoDAO The implementation of the delegable OAuth2 auth info DAO. - * @param openIDInfoDAO The implementation of the delegable OpenID auth info DAO. - * @return The auth info repository instance. + * XXX This should be done the same way as HTTP filters. */ @Provides def provideAuthInfoRepository( @@ -243,7 +240,6 @@ class SilhouetteModule extends AbstractModule with ScalaModule { oauth1InfoDAO: DelegableAuthInfoDAO[OAuth1Info], oauth2InfoDAO: DelegableAuthInfoDAO[OAuth2Info], openIDInfoDAO: DelegableAuthInfoDAO[OpenIDInfo]): AuthInfoRepository = { - new DelegableAuthInfoRepository(totpInfoDAO, passwordInfoDAO, oauth1InfoDAO, oauth2InfoDAO, openIDInfoDAO) } @@ -404,6 +400,13 @@ class SilhouetteModule extends AbstractModule with ScalaModule { new GoogleProvider(httpLayer, socialStateHandler, configuration.underlying.as[OAuth2Settings]("silhouette.google")) } + @Provides + def provideGitHubProvider(httpLayer: HTTPLayer, + socialStateHandler: SocialStateHandler, + configuration: Configuration): GitHubProvider = { + new GitHubProvider(httpLayer, socialStateHandler, configuration.underlying.as[OAuth2Settings]("silhouette.github")) + } + /** * Provides the VK provider. * diff --git a/conf/application.prod.conf b/conf/application.prod.conf index 742922e..9a48862 100644 --- a/conf/application.prod.conf +++ b/conf/application.prod.conf @@ -1,5 +1,6 @@ -include "application.conf" +include "application" +# Use the "playGenerateSecret" sbt task to generate keys here. play.http.secret.key=${?PLAY_APP_SECRET} # Allow all proxies for Heroku so that X-Forwarded headers can be read by Play @@ -19,40 +20,32 @@ play.mailer { } silhouette { + callback.host = ${SILHOUETTE_CALLBACK_HOST} + + callback.scheme = "https" + callback.scheme = ${?SILHOUETTE_CALLBACK_SCHEME} + + cookie.secure = true + cookie.secure = ${?SILHOUETTE_COOKIE_SECURE} # Authenticator settings - authenticator.cookieDomain="play-silhouette-seed.herokuapp.com" - authenticator.secureCookie=true + authenticator.cookieDomain=${silhouette.callback.host} # OAuth1 token secret provider settings - oauth1TokenSecretProvider.cookieDomain="play-silhouette-seed.herokuapp.com" - oauth1TokenSecretProvider.secureCookie=true + oauth1TokenSecretProvider.cookieDomain=${silhouette.callback.host} # OAuth2 state provider settings - oauth2StateProvider.cookieDomain="play-silhouette-seed.herokuapp.com" - oauth2StateProvider.secureCookie=true - - # Facebook provider - facebook.redirectURL="https://play-silhouette-seed.herokuapp.com/authenticate/facebook" - - # Google provider - google.redirectURL="https://play-silhouette-seed.herokuapp.com/authenticate/google" - - # VK provider - vk.redirectURL="https://play-silhouette-seed.herokuapp.com/authenticate/vk" - - # Twitter provider - twitter.callbackURL="https://play-silhouette-seed.herokuapp.com/authenticate/twitter" - - # Xing provider - xing.callbackURL="https://play-silhouette-seed.herokuapp.com/authenticate/xing" - - # Yahoo provider - yahoo.callbackURL="https://play-silhouette-seed.herokuapp.com/authenticate/yahoo" - yahoo.realm="https://play-silhouette-seed.herokuapp.com" + oauth2StateProvider.cookieDomain=${silhouette.callback.host} + + # Use the "playGenerateSecret" sbt task to generate keys here. + # DO NOT REUSE THE SAME KEY, THIS IS CRYPTOGRAPHICALLY INSECURE. + oauth1TokenSecretProvider.signer.key = ${OAUTH1_SIGNER_KEY} + oauth1TokenSecretProvider.crypter.key = ${OAUTH1_CRYPTER_KEY} + socialStateHandler.signer.key = ${SOCIAL_SIGNER_KEY} + csrfStateItemHandler.signer.key = ${CSRF_SIGNER_KEY} } play.filters.hosts { - # Allow requests to heroku, its subdomains, and localhost:9000. - allowed = [".herokuapp.com", "localhost:9000"] + # Allow requests to the host and localhost:9000. + allowed = [${silhouette.callback.host}, "localhost:9000"] } diff --git a/conf/silhouette.conf b/conf/silhouette.conf index 9492d97..a6c9e60 100644 --- a/conf/silhouette.conf +++ b/conf/silhouette.conf @@ -1,103 +1,73 @@ silhouette { + callback.host = "localhost:9000" + callback.host=${?SILHOUETTE_CALLBACK_HOST} + + callback.scheme = "http" + callback.scheme=${?SILHOUETTE_CALLBACK_SCHEME} + callback.url = ${silhouette.callback.scheme}"://"${silhouette.callback.host} + + // Disabled for testing on localhost without SSL, otherwise cookie couldn't be set + cookie.secure = false + cookie.path = "/" # Authenticator settings - authenticator.cookieName="authenticator" - authenticator.cookiePath="/" - authenticator.secureCookie=false // Disabled for testing on localhost without SSL, otherwise cookie couldn't be set - authenticator.httpOnlyCookie=true - authenticator.sameSite="Lax" - authenticator.useFingerprinting=true - authenticator.authenticatorIdleTimeout=30 minutes - authenticator.authenticatorExpiry=12 hours + authenticator { + cookieName="authenticator" + cookiePath=${silhouette.cookie.path} + secureCookie=${silhouette.cookie.secure} + httpOnlyCookie=true + sameSite="Lax" + useFingerprinting=true + authenticatorIdleTimeout=30 minutes + authenticatorExpiry=12 hours - authenticator.rememberMe.cookieMaxAge=30 days - authenticator.rememberMe.authenticatorIdleTimeout=5 days - authenticator.rememberMe.authenticatorExpiry=30 days + rememberMe.cookieMaxAge=30 days + rememberMe.authenticatorIdleTimeout=5 days + rememberMe.authenticatorExpiry=30 days - authenticator.signer.key = "[changeme]" // A unique encryption key - authenticator.crypter.key = "[changeme]" // A unique encryption key + signer.key = "[changeme]" // A unique encryption key + signer.key = ${?AUTH_SIGNER_KEY} + + crypter.key = "[changeme]" // A unique encryption key + crypter.key = ${?AUTH_CRYPTER_KEY} + } # OAuth1 token secret provider settings - oauth1TokenSecretProvider.cookieName="OAuth1TokenSecret" - oauth1TokenSecretProvider.cookiePath="/" - oauth1TokenSecretProvider.secureCookie=false // Disabled for testing on localhost without SSL, otherwise cookie couldn't be set - oauth1TokenSecretProvider.httpOnlyCookie=true - oauth1TokenSecretProvider.sameSite="Lax" - oauth1TokenSecretProvider.expirationTime=5 minutes + oauth1TokenSecretProvider { + cookieName="OAuth1TokenSecret" + cookiePath=${silhouette.cookie.path} + secureCookie=${silhouette.cookie.secure} + httpOnlyCookie=true + sameSite="Lax" + expirationTime=5 minutes - oauth1TokenSecretProvider.signer.key = "[changeme]" // A unique encryption key - oauth1TokenSecretProvider.crypter.key = "[changeme]" // A unique encryption key + signer.key = "[changeme]" // A unique encryption key + crypter.key = "[changeme]" // A unique encryption key + } + + # # OAuth2 state provider settings + oauth2StateProvider.secureCookie=${silhouette.cookie.secure} # Social state handler socialStateHandler.signer.key = "[changeme]" // A unique encryption key # CSRF state item handler settings - csrfStateItemHandler.cookieName="OAuth2State" - csrfStateItemHandler.cookiePath="/" - csrfStateItemHandler.secureCookie=false // Disabled for testing on localhost without SSL, otherwise cookie couldn't be set - csrfStateItemHandler.httpOnlyCookie=true - csrfStateItemHandler.sameSite="Lax" - csrfStateItemHandler.expirationTime=5 minutes - - csrfStateItemHandler.signer.key = "[changeme]" // A unique encryption key + csrfStateItemHandler { + cookieName="OAuth2State" + cookiePath=${silhouette.cookie.path} + secureCookie=${silhouette.cookie.secure} + httpOnlyCookie=true + sameSite="Lax" + expirationTime=5 minutes - # Facebook provider - facebook.authorizationURL="https://graph.facebook.com/v2.3/oauth/authorize" - facebook.accessTokenURL="https://graph.facebook.com/v2.3/oauth/access_token" - facebook.redirectURL="http://localhost:9000/authenticate/facebook" - facebook.clientID="" - facebook.clientID=${?FACEBOOK_CLIENT_ID} - facebook.clientSecret="" - facebook.clientSecret=${?FACEBOOK_CLIENT_SECRET} - facebook.scope="email" - - # Google provider - google.authorizationURL="https://accounts.google.com/o/oauth2/auth" - google.accessTokenURL="https://accounts.google.com/o/oauth2/token" - google.redirectURL="http://localhost:9000/authenticate/google" - google.clientID="" - google.clientID=${?GOOGLE_CLIENT_ID} - google.clientSecret="" - google.clientSecret=${?GOOGLE_CLIENT_SECRET} - google.scope="profile email" - - # VK provider - vk.authorizationURL="http://oauth.vk.com/authorize" - vk.accessTokenURL="https://oauth.vk.com/access_token" - vk.redirectURL="http://localhost:9000/authenticate/vk" - vk.clientID="" - vk.clientID=${?VK_CLIENT_ID} - vk.clientSecret="" - vk.clientSecret=${?VK_CLIENT_SECRET} - vk.scope="email" - - # Twitter provider - twitter.requestTokenURL="https://twitter.com/oauth/request_token" - twitter.accessTokenURL="https://twitter.com/oauth/access_token" - twitter.authorizationURL="https://twitter.com/oauth/authenticate" - twitter.callbackURL="http://localhost:9000/authenticate/twitter" - twitter.consumerKey="" - twitter.consumerKey=${?TWITTER_CONSUMER_KEY} - twitter.consumerSecret="" - twitter.consumerSecret=${?TWITTER_CONSUMER_SECRET} - - # Xing provider - xing.requestTokenURL="https://api.xing.com/v1/request_token" - xing.accessTokenURL="https://api.xing.com/v1/access_token" - xing.authorizationURL="https://api.xing.com/v1/authorize" - xing.callbackURL="http://localhost:9000/authenticate/xing" - xing.consumerKey="" - xing.consumerKey=${?XING_CONSUMER_KEY} - xing.consumerSecret="" - xing.consumerSecret=${?XING_CONSUMER_SECRET} - - # Yahoo provider - yahoo.providerURL="https://me.yahoo.com/" - yahoo.callbackURL="http://localhost:9000/authenticate/yahoo" - yahoo.axRequired={ - "fullname": "http://axschema.org/namePerson", - "email": "http://axschema.org/contact/email", - "image": "http://axschema.org/media/image/default" + signer.key = "[changeme]" // A unique encryption key } - yahoo.realm="http://localhost:9000" } + +include "social/facebook" +include "social/github" +include "social/google" +include "social/twitter" +include "social/vk" +include "social/xing" +include "social/yahoo" \ No newline at end of file diff --git a/conf/social/facebook.conf b/conf/social/facebook.conf new file mode 100644 index 0000000..e8c2460 --- /dev/null +++ b/conf/social/facebook.conf @@ -0,0 +1,12 @@ +silhouette { + facebook { + authorizationURL="https://graph.facebook.com/v2.3/oauth/authorize" + accessTokenURL="https://graph.facebook.com/v2.3/oauth/access_token" + redirectURL=${silhouette.callback.url}"/authenticate/facebook" + clientID="" + clientID=${?FACEBOOK_CLIENT_ID} + clientSecret="" + clientSecret=${?FACEBOOK_CLIENT_SECRET} + scope="email" + } +} \ No newline at end of file diff --git a/conf/social/github.conf b/conf/social/github.conf new file mode 100644 index 0000000..050e05d --- /dev/null +++ b/conf/social/github.conf @@ -0,0 +1,12 @@ +silhouette { + github { + authorizationURL="https://github.com/login/oauth/authorize" + accessTokenURL="https://github.com/login/oauth/access_token" + redirectURL=${silhouette.callback.url}"/authenticate/github" + clientID="" + clientID=${?GITHUB_CLIENT_ID} + clientSecret="" + clientSecret=${?GITHUB_CLIENT_SECRET} + scope="profile email" + } +} diff --git a/conf/social/google.conf b/conf/social/google.conf new file mode 100644 index 0000000..2ba9b46 --- /dev/null +++ b/conf/social/google.conf @@ -0,0 +1,13 @@ +silhouette { + google { + authorizationURL="https://accounts.google.com/o/oauth2/auth" + accessTokenURL="https://accounts.google.com/o/oauth2/token" + redirectURL=${silhouette.callback.url}"/authenticate/google" + clientID="" + clientID=${?GOOGLE_CLIENT_ID} + clientSecret="" + clientSecret=${?GOOGLE_CLIENT_SECRET} + scope="profile email openid" + } + +} diff --git a/conf/social/twitter.conf b/conf/social/twitter.conf new file mode 100644 index 0000000..77e3d30 --- /dev/null +++ b/conf/social/twitter.conf @@ -0,0 +1,15 @@ +silhouette { + twitter { + requestTokenURL="https://twitter.com/oauth/request_token" + accessTokenURL="https://twitter.com/oauth/access_token" + authorizationURL="https://twitter.com/oauth/authenticate" + + callbackURL=${silhouette.callback.url}"/authenticate/twitter" + + # Keys and Tokens / Consumer API keys + consumerKey="" + consumerKey=${?TWITTER_CONSUMER_KEY} + consumerSecret="" + consumerSecret=${?TWITTER_CONSUMER_SECRET} + } +} diff --git a/conf/social/vk.conf b/conf/social/vk.conf new file mode 100644 index 0000000..2db59e1 --- /dev/null +++ b/conf/social/vk.conf @@ -0,0 +1,10 @@ +silhouette { + vk.authorizationURL="http://oauth.vk.com/authorize" + vk.accessTokenURL="https://oauth.vk.com/access_token" + vk.redirectURL=${silhouette.callback.url}"/authenticate/vk" + vk.clientID="" + vk.clientID=${?VK_CLIENT_ID} + vk.clientSecret="" + vk.clientSecret=${?VK_CLIENT_SECRET} + vk.scope="email" +} \ No newline at end of file diff --git a/conf/social/xing.conf b/conf/social/xing.conf new file mode 100644 index 0000000..76b8a65 --- /dev/null +++ b/conf/social/xing.conf @@ -0,0 +1,10 @@ +silhouette { + xing.requestTokenURL="https://api.xing.com/v1/request_token" + xing.accessTokenURL="https://api.xing.com/v1/access_token" + xing.authorizationURL="https://api.xing.com/v1/authorize" + xing.callbackURL=${silhouette.callback.url}"/authenticate/xing" + xing.consumerKey="" + xing.consumerKey=${?XING_CONSUMER_KEY} + xing.consumerSecret="" + xing.consumerSecret=${?XING_CONSUMER_SECRET} +} \ No newline at end of file diff --git a/conf/social/yahoo.conf b/conf/social/yahoo.conf new file mode 100644 index 0000000..a2f6753 --- /dev/null +++ b/conf/social/yahoo.conf @@ -0,0 +1,10 @@ +silhouette { + yahoo.providerURL="https://me.yahoo.com/" + yahoo.axRequired={ + "fullname": "http://axschema.org/namePerson", + "email": "http://axschema.org/contact/email", + "image": "http://axschema.org/media/image/default" + } + yahoo.callbackURL=${silhouette.callback.url}"/authenticate/yahoo" + yahoo.realm=${silhouette.callback.url} +} \ No newline at end of file diff --git a/public/images/providers/github.png b/public/images/providers/github.png new file mode 100644 index 0000000..182a1a3 Binary files /dev/null and b/public/images/providers/github.png differ