FacebookSDKがCakePHPのSessionを使うようにする

cakePHPでFacebookログイン[1/2] – もぎゃろぐで書いたとおり、
FacebookSDKをつかうと簡単にfacebookログインを実装することができます。,

FacebookSDKは、この簡単実装を実現するために、内部で$_SESSIONを呼び出して、accessTokenや CSRF対策のstate変数を保存しています。
この実装は、単純なWEBサーバであれば困らないのですが、複数のWEBサーバで分散処理するためにセッションをDBに保存する、というような設定をしている場合、 このままだと動かないことが予想されます。

CakePHPは、そういうときのためにセッションをデータベースに保存したりすることができるようになっていますので、FacebookSDKの変数も、CakePHPのセッションを使ってもらうようにしたいところです。

facebookのエンジニアの人たちは優秀なので、こういう用途も想定してSDKを書かれたようです。

facebookSDKのFacebookクラスは、ほとんどの実装がbase_facebook.phpのBaseFacebookクラスで実装されて、上記セッション変数の処理だけが、facebook.phpのFacebookクラスで実装されています。
なので、facebook.phpをコピーしたcakeFacebook.phpを作って、$_SESSIONの部分をcakePHPのCakeSessionに書き換えたCakeFacebookクラスを用意すれば、FacebookSDKもcakePHPの設定に従ってくれるようになります。

ということで出来上がったのがこちらになります。

facebook.phpと同じディレクトリに配置して

App::import(‘Vendor’, ‘facebook/cakeFacebook’);

$this->facebook = new CakeFacebook(array(
‘appId’ => Configure::read(‘facebook.appid’),
‘secret’ => Configure::read(‘facebook.secret’),
));

という具合にして使います。あとは通常のFacebookクラスと同じ使い方でOKです。

cakePHPでFacebookログイン

前の記事で予想がついている方もおられるかと思いますが、facebookログインするサイトを作っています。
RubyじゃなくてPHPで。人生何が起こるかわからないものですね。

さすがに生のPHPは書いてられないので、CakePHPを使っています。
ということで、今日の記事は、CakePHP+FacebookSDKです。
ぐぐれば似たような記事はいくつか出てくるのですが、いずれも記述が古くて動かなかったので、僕バージョンをまとめました。これも半年位すると動かなくなるのかもしれないですが。

さて、PHPでFacebookのOAuth2.0ログインを実装するやり方については、公式サイトに記述があります。

Login for Server-side Apps – Facebook Developers

ただこれだとAPI呼び出しが面倒なので、Facebook公式のfacebook-php-sdkをつかいます。
facebook-php-sdkの利用サンプルも、ぐぐればたくさん出てきますが、その大半は古い内容で現在のSDKでは動きません(getAccessToken()をわざわざ呼び出しているのは古いコードです。)
Migrating to OAuth 2.0 update: PHP SDK v.3.1.1- Facebook Developersを参考に書き直したのがこちら。

要するに、$facebook->getUser()して取れたらログイン成功、取れなかったらログインURLにリダイレクトすればOKです。昔のSDKで必要だったgetAccessTokenの呼び出しは、SDKが内部で処理してくれるようになりました。
コールバック引数を見てアレコレする部分もSDKが面倒を見てくれるので、login.phpとcallback.phpのように二つのファイルを作る必要すらありません。PHP用のSDKらしい実装ですね。

これを踏まえて、CakePHPでFacebookContollerを書いたのがこちら。

  • facebook SDKは、/app/Vendor/facebook/の下に配置。
  • appidとsecretは、bootstrap.phpに
    Configure::write('facebook',array(
    'appid'=>'***',
    'secret'=>'***'
    ));
    

    という具合に。

一応loginとcallbackを分けておきましたが、getUser()部分をbeforeFilterに移動させれば、ひとつのメソッドにまとめてしまうことも可能だと思います。
もっといえば、FacebookControllerというクラスを用意しなくても、ほかのControllerのbeforeFilterに書いてしまってもかまわないかも知れないですね。

[iOS] タブを残さずURLスキームを開く

TitaniumのユーザーBBSで、ChatWork のiPhoneアプリの挙動が話題になっています。

ChatWork の謎 | Don’t Fall – Titanium Mobileユーザー会サポートBBS

ChatWorkアプリでは、facebook認証でアプリを使うことが出来るのですが、これまでよく使われていたWebView上での認証ではなくて、Ti.Platform.openURL() を使ってブラウザを立ち上げてfacebook認証を行っています。

WebViewを使わない理由は、「OAuthの認証にWebViewを使うのはやめよう – Shogo’s Blog」に書かれているとおりだと思います。具体的な実装としてChatWorkアプリはとても参考になりますね。

さて、ブラウザ上でfacebook認証が成功すると、ChatWorkサーバはURLスキームを使ってChatWorkアプリケーションを起動させます。
ここがマニアックに興味深いところです。

  • ユーザーはリンクをクリックしていないのに、なぜURLスキームが起動したのか?
  • ChatWorkからブラウザに戻ったとき、URLスキームを起動したWEBページが残っていないのは何故?他のアプリだと残っちゃうよね?

結論から言うと、こういうふうに書けば同じ挙動が再現できました。

(本当は”chatwork:///”でChatWorkアプリが起動するのですけど、テストのために意味不明な起動を繰り返すとご迷惑になりそうだったので、メッセージアプリを起動しています)

ページを読み込むと同時にURLスキームを起動させる

 URLスキームは普通、aタグのhrefに書いて、ユーザーさんにクリックしてもらって使います。例えば、

<a href="sms:///">sms</a>

と書かれたHTMLのリンクをユーザーさんがクリックすると、メッセージアプリが立ち上がります。でも、facebook認証が済んだら、ユーザーさんの手を煩わせることなくアプリケーションに戻ってほしいですよね。

 location.hrefで強制移動とか、aタグを書いてjQuery(hoge).click();とか、いろいろ試してみたのですけど、そういうのは駄目なようです。
ページ内に動的にiFrameを生成してURLスキームを持たせることで上手く起動させることが出来ました。

URLスキームを起動したあと自分を閉じる

 URLスキームでアプリを起動したのはいいものの、そのHTMLがブラウザ上に残ったままになっていると、あとでブラウザを開き直した時に変なページが見えてしまったり、場合によっては勝手にアプリが立ち上がってイラっとさせられることがあります。結構有名なアプリでもこの現象起こしますよね。

 JavaScriptで自分を表示しているタブを閉じる場合、理屈上はwindow.close()で出来るのですけど、safariのwindow.closeはjavascriptで開いたページしかclose出来ないので、このようなごまかしテクニックを使うことで自分をcloseすることが出来ます。

window.opener = window;
var win = window.open(location.href,”_self”);
win.close();

URLスキームとwindow.close、どっちかが他方を妨害しそうな気がしますけど、実際には両方上手く動くみたいです。

P.S 1:自分でJavaScript書いて同様の挙動を再現させることに成功しただけなので、ChatWorkさんのアプリと厳密には一致しないかも知れません。
P.S 2:[iOS]って書いてますが、URLスキームをintentに変えればAndroidでもそのまま動作しました。
P.S 3:ChatWorkの中の方、出来るだけ迷惑かけないように調査したつもりですけど、不審なログとして調査対象に上がっていたりしたらごめんなさい。