SecureSocialで独自の認証画面を作る。

環境は

  • Scala : 2.11
  • Play : 2.3
  • SecureSocial : 3.0-M1

SecureSocialで独自の認証画面を使う方法を書く。 前提として、以下の方法でsecuresocialを導入しているものとする。

Global.scala

RuntimeEnvironmentのサブクラスを実装する。 ほぼ、前回と同じだが、カスタムの画面を使用する場合はviewTemplatesをオーバーライドする。

object ApplicationRuntimeEnvironment extends RuntimeEnvironment.Default[User] {
    protected override def include(p: IdentityProvider) = p.id ->   p
    protected override def oauth1ClientFor(provider: String) = new OAuth1Client.Default(ServiceInfoHelper.forProvider(provider), httpService)
    protected override def oauth2ClientFor(provider: String) = new OAuth2Client.Default(httpService, OAuth2Settings.forProvider(provider))

    override lazy val userService: UserService[User] = new UserServiceImpl
    override lazy val providers = ListMap(
        include(new FacebookProvider(routes, cacheService, oauth2ClientFor(FacebookProvider.Facebook))),
        include(new GoogleProvider(routes, cacheService,oauth2ClientFor(GoogleProvider.Google))),
        include(new UsernamePasswordProvider[User](userService, avatarService, viewTemplates, passwordHashers))
    )

    override lazy val viewTemplates 
        = new plugins.CustomTemplates(this) /// <====追加
}

plugins/CustomTemplates.scala

ViewTemplatesを継承したクラスを作成。 独自に実装したい画面に対応するメソッドをオーバーライドする。FacebookなどのSNSでのログインのみであれば、getLoginPage、ID/パスワードの認証であれば、その他のメソッドもオーバーライドする必要がある

  • getLoginPage
  • getNotAuthorizedPage
  • getSignUpPage
  • getStartSignUpPage
  • getResetPasswordPage
  • getStartResetPasswordPage
  • getPasswordChangePage

import play.api.data.Form
import play.api.i18n.Lang
import play.api.mvc.RequestHeader
import play.twirl.api.Html

import securesocial.controllers._
import securesocial.core.RuntimeEnvironment

class CustomTemplates(env: RuntimeEnvironment[_]) extends ViewTemplates {
    implicit val implicitEnv = env

    override def getLoginPage(form: Form[(String, String)],
            msg: Option[String] = None)(implicit request: RequestHeader, lang: Lang): Html = {
        views.html.login(form, msg)
    }
    ...(略)... 
}

あとはテンプレートを書けばいいだけなので、特に問題となるようなところは無いはず。 わからなければ、ココらへんに転がってるものを見ればいいと思う。

https://github.com/jaliss/securesocial/tree/master/module-code/app/securesocial/views

views.login.scala.html

@(loginForm: Form[(String,String)], errorMsg: Option[String] = None)(implicit request: RequestHeader, env:securesocial.core.RuntimeEnvironment[_])

@import java.util._
@import java.text.DateFormat

@import securesocial.core.providers.UsernamePasswordProvider.UsernamePassword

@main {
<div class="form-box" id="login-box">
    <div class="header">Sign In</div>
    @env.providers.get(UsernamePassword).map { up =>
        @views.html.provider("userpass", Some(loginForm))
    }
    <div class="margin text-center">
        @defining( env.providers.values.filter( _.id != UsernamePassword) ) { externalProviders =>
            @if( externalProviders.size > 0 ) {
                <div class="clearfix">
                    <p>@Messages("securesocial.login.instructions")</p>
                    <p>
                        @for(p <- externalProviders) {
                            @views.html.provider(p.id)
                        }
                    </p>
                </div>
            }
        }

    </div>
</div>

}{
}{
}

views.provider.scala.html

@(providerId: String, loginForm: Option[Form[(String, String)]] = None)(implicit request: RequestHeader, lang: Lang, env:     securesocial.core.RuntimeEnvironment[_])

@import securesocial.core.providers.UsernamePasswordProvider
@import securesocial.core.AuthenticationMethod._
@import play.api.Logger
@import helper._
@import play.api.Play

@implicitFieldConstructor = @{ FieldConstructor(securesocial.views.html.inputFieldConstructor.f) }

@env.providers.get(providerId).map { provider =>
@if( provider.authMethod == OAuth1 || provider.authMethod == OAuth2 ) {
    @defining( "images/providers/%s.png".format(provider.id) ) { imageUrl =>
        <a href="@env.routes.authenticationUrl(provider.id)"> <img src="@controllers.routes.Assets.at(imageUrl)"/></a>
    }
}

@if( provider.authMethod == UserPassword ) {
    <form action="@env.routes.authenticationUrl("userpass")" method="post">
    <div class="body bg-gray">
        @if(loginForm.get.hasErrors) {
        <div class="alert alert-danger alert-dismissable">
            <i class="fa fa-ban"></i>
            <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
            User ID or password is invalid!
        </div>
        }
        @if( UsernamePasswordProvider.withUserNameSupport ) {
            @helper.inputText(
                loginForm.get("username"),
                '_label -> Messages("securesocial.signup.username"),
                'class -> "form-control",
                'placeholder -> "User ID"
            )
        } else {
            @helper.inputText(
                loginForm.get("username"),
                '_label -> Messages("securesocial.signup.email1"),
                'class -> "form-control",
                'placeholder -> "User ID"
            )
        }

        @helper.inputPassword(
            loginForm.get("password"),
            '_label -> Messages("securesocial.signup.password1"),
            'class -> "form-control",
            'placeholder -> "Password"
        )
        <div class="form-group">
            <input type="checkbox" name="remember_me"/> Remember me
        </div>
    </div>
    <div class="footer">
        <button type="submit" class="btn bg-olive btn-block">Sign me in</button>

        <p><a href="@env.routes.resetPasswordUrl">I forgot my password</a></p>

        @if(Play.current.configuration.getBoolean("securesocial.registrationEnabled").getOrElse(true) ){
            <a href="@env.routes.signUpUrl" class="text-center">Register a new membership</a>
        }
    </div>
    </form>
    }
}.getOrElse {
    @*****************************************
    * Todo: throw a runtime exception? this might need improvement
    *****************************************@
    @Logger.error("[securesocial] unknown provider '%s'. Can't render it.".format(providerId))
    { throw new RuntimeException("Unknown provider '%s') }
}

実行

あとは、実行するだけ。簡単にログイン画面のカスタマイズができた。

f:id:H_Yamaguchi:20150216193241p:plain

最後に

  • securesocialのバージョンが3.0になって変わったのは、play.pluginsを使わなくなった(かわりにRuntimeEnvironmentを使う)だけっぽい。
  • 認証時のメールのテンプレートなどもカスタマイズできる。その場合は、RuntimeEnvironmentのmailTemplatesをオーバーライドすればいい。