自力でFacebookのOAuth2に対応してみた
やることはFacebookアカウントでサイトのアカウント作って、ログイン認証したいだけだったのだが、どうも Playframework2.2 では Pac4J が対応していなかったので、自力で認証してみた。
まずは設定に Facebook のアプリケーション情報を突っ込む
auth { facebook { app_id="XXXXXXXX" app_secret="YYYYYYYYYYYYYYYYYY" } }
そしたらログイン用のコントローラを作成します。名前は適当に。で、Action は必ず routes に突っ込んでください。
object Facebook extends Controller
まず、設定を読み込みます。
import play.api.Play.current val APP_ID = current.configuration.getString("auth.facebook.app_id").getOrElse("") val APP_SECRET = current.configuration.getString("auth.facebook.app_secret").getOrElse("") val AUTH_CSRF = "FACEBOOK_AUTH_CSRF_KEY"
ログイン用のアクションと、そのコールバックを指定します。
def login = Action { implicit request => Ok("Dummy") } def callback(hash:String) = Action { implicit request => Ok("Calledback") }
callback の引数 hash はCSRF対策に使います。
routes に記述したら、まずは login 側から作りこみ
def getCallbackUrl(csrfToken:String)(implicit request:Request[AnyContent]) = { URLEncoder.encode(routes.Facebook.callback(csrfToken).absoluteURL(false), "UTF-8") } def login = Action { implicit request => val csrfToken = Crypto.generateToken val authURL = "http://www.facebook.com/dialog/oauth?client_id=%s&redirect_uri=%s".format(APP_ID, getCallbackUrl(csrfToken)) Redirect(authURL).withSession(AUTH_CSRF -> csrfToken) }
absoluteURL の引数は、本番で HTTPS 居れたら true に設定してください。
やってることは、CSRFトークン混みで、Facebook の認証が通った後のリダイレクト先URLを指定します。
OAuth2の仕様上、本来は https のみで扱う筈ですが、開発中なので今は気にしません。
次はコールバック側。
こちらは、受け取った瞬間に、CSRF をチェックし、正常であれば Facebook からアクセストークンの取得、及び、ユーザ情報を受け取ります。
def getAccessToken(hash:String)(implicit request:Request[AnyContent]) = { val requestCode = request.getQueryString("code").getOrElse("") val token_url = "https://graph.facebook.com/oauth/access_token?client_id=" + APP_ID + "&redirect_uri=" + getCallbackUrl(hash) + "&client_secret=" + APP_SECRET + "&code=" + requestCode val result = WS.url(token_url).get.map(_.body) Await.result(result, 1 second) } def getUserJson(accessToken:String)(implicit request:Request[AnyContent]) = { val token_url = "https://graph.facebook.com/me?" + accessToken val result = WS.url(token_url).get.map(_.json) Await.result(result, 1 second) } def csrfCheck[T](urlToken:String, f: => T)(implicit request:Request[AnyContent]) = { session.get(AUTH_CSRF).map(csrfToken => { if (csrfToken == csrfToken) { f } else { throw new Exception("CSRF error.") } }).getOrElse(throw new Exception("No session.")) } def callback(hash:String) = Action { implicit request => csrfCheck(hash, { val token = getAccessToken(hash) val userInfo = getUserJson(token) import play.api.libs.json._ Ok(Json.stringify(userInfo)) }) }
成功すると、下記項目がJSONで拾ってこれます。
- id : 多分数値(ユニーク)
- name : フルネーム
- first_name
- last_name
- link : ユーザのタイムラインページ
- birthday : 誕生日
- hometown : 住所
- location : 現在座標?
- work : 会社情報
- education : 学歴
何も気にせず Facebook のアプリに OK 押すと、これだけの情報が引っこ抜かれるわけで、、、。
お気を付けくださいだわなぁ。
よくあるミス
- リクエスト先 URL が間違ってる
- リクエストパラメータに過不足がある(多くてもダメな場合がある)
- http/https を間違えてる
これに普通に 3h とか悩まされた。こんなもん自分で作るもんじゃないよ!