しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET MVC で Facebook アプリを作る時には気をつけろ

最近は ASP.NET MVC 向けの Facebook アプリケーションテンプレートを使って、Facebook アプリの開発をしていたりするんですが、この Microsoft.AspNet.Mvc.Facebook というライブラリが非常に使い勝手が悪く、特定のアプリしか想定していない作りになっていて、めちゃくちゃ困らされっぱなしです。

次からは同じ失敗をしないために、そして開発者にフィードバックをするためにもメモしておきます。

ちなみに、参考になりそうでならない公式チュートリアルは ASP.NET MVC Facebook Birthday App : The Official Microsoft ASP.NET Site です。

認証について

FacebookAuthorize 属性を付けたアクションや、Html.FacebookSignedRequest ヘルパーを使って POST されたアクションの内部では 、フォーム認証を使ってクッキーが発行されていても User.Identity.IsAuthenticated が常に false を返してきます。

これはデフォルトでは Facebook に特化した HttpModule が自動的に有効になり、User プロパティの値がオーバーライドされてしまうので、こいつを無効にします。

無効にするには Web.config の appSettings セクションに以下の設定を追加するだけです。

<add key="Facebook:DisableAuthenticationModule" value="true" />

このモジュールを使うと Facebook の signed_request をクレームベースの認証として扱うことが出来るのですが、実装した人間にお前は signed_request を常に引き継がせたいのかと、小一時間問い詰めたくなります。

ちなみに、よく Facebook ページで使われるページタブと言う種類のアプリ開発では、FacebookAuthorize 属性を使うと上手く動作しないので頑張りましょう。*1

アクセストークンとクライアント

公式のチュートリアルではアクション全てに FacebookAuthorize 属性を付けて、引数で FacebookContext をバインディングするようにしていますが、毎回リダイレクトするようなページはユーザー体験的には最低ですよね。

最初に signed_request が渡ってきた時に、そのユーザーのアクセストークンが取れるわけなので、それを保存しておいて使えばいいわけです。

しかし、そのアクセストークンを使うには FacebookClient クラスが必要になるわけですが、こいつの作り方がまた罠っぽくなっています。

// AppId, AppSecret, JSON シリアライザがセットされた FacebookClient を作成できる
var client = GlobalFacebookConfiguration.Configuration.ClientProvider.CreateClient();

// アクセストークンは後からセット
client.AccessToken = "...";

ClientProvider を使わずに自力で FacebookClient を作成すると、高確率で SetJsonSerializers の呼び出しを忘れて悩む羽目になるのでお勧めできません。*2

Facebook C# SDK はもうちょっと依存関係を持ってもいいと思いました。

// ClientProvider を使わずに Facebook クライアントを作成
var client = new FacebookClient(accessToken)
{
    AppId = "...",
    AppSecret = "...",
};

// クラスとしてデシリアライズする場合には JSON シリアライザの設定が必要だが、あえてコメントアウト
//client.SetJsonSerializers(JsonConvert.SerializeObject, JsonConvert.DeserializeObject);

// Facebook の友達を取得する
var friends = await client.GetCurrentUserFriendsAsync<FacebookFriend>();

// デシリアライズ出来ず friends は null になるので、このコードは即死する
Console.WriteLine(friends.Count);

指定したクラスにデシリアライズが出来ない時点で、例外を投げるべきだと思うのは僕だけでしょうか?

FacebookClient のインスタンスは FacebookContext に用意されている Client プロパティからも取れますが、そちらを使うと上手くデシリアライズ出来るというのが被害を拡大させました。

パーミッションとリダイレクト

FacebookAuthorize 属性で指定したパーミッションが許可されていない、もしくはアプリ自体が許可されていない場合には、実際の認証周りの処理を行う FacebookAuthorizeFilter クラスが Facebook のログイン URL へリダイレクトするコードを吐き出します。

<add key="Facebook:AuthorizationRedirectPath" value="~/Home/Permissions" />

デフォルトで用意されている ~/Home/Permissions という URL は「この先は~というパーミッションが必要ですよ」という表示を行うためのものです。が、ここで許可をしたつもりでも、Facebook 側で本番の許可ダイアログが表示されます。

このページの存在意義がぶっちゃけ分からないので、URL を指定せずに空っぽにしておきます。

<!-- 2 回も表示しなくていい -->
<add key="Facebook:AuthorizationRedirectPath" value="" />

これで Facebook 側の許可ダイアログに直行するようになります。

関係ないですが、テンプレートに書かれている以下の設定は使われていないみたいです。

<add key="Facebook:VerifyToken:User" value="" />

消し忘れかもしれないですが、よくわかりません。

とにかく、まだまだ Facebook テンプレートには罠が多そうなので、頑張って使ってノウハウを貯めてフィードバックの必要がありそうです。

*1:イラッとしたのでページタブ用に改造した。後悔はしていない。

*2:今日はこれで 1 時間は無駄にした気がする