ほら、 Facebook の canvasアプリ
やら page tabアプリ
やら作る時って、 iframe
にサイトを表示させるじゃないですか。
そうすると、 3rd party cookie
について
思いを馳せないとならんわけですよ。
普通に、 OAuth
すると、facebook
の iframe x-frame-options
のセキュリティー・ポリシーにより、画面が遷移しない。
ここで、なぜ自分の作ったFacebookアプリが facebook
の iframe x-frame-options
のセキュリティー・ポリシーにかかるかというと、Facebook ページタブ内のページは facebook のプロキシ介しているんですね。
なので、そういう事が起こる。
ではどう対応するかと
- omniauthの
iframe
オプションで回避- これだと
top
のwindow
でOAuth
できるんだけど、返ってきたあとFacebook ページタブではなく普通に外部サイトに返ってしまう。 - cookieが別になってしまう
- これだと
- FB JavaScript SDK で先に
OAuth
してしまう。- 今回はこれ
- 目からウロコだった
テスト書くのが結構たいへんだったけど
FB JavaScript SDK
の stub
使ったり、して何とか対応出来た。
FB JavaScript SDK で先に OAuth
してしまう
omniauth-facebook
の READMEには Canvas Apps の項がありまして、コードになおすると以下の様な感じでいけると書いてあるですよね。
FB.init(appId: FB_APP_ID, status: true, cookie: true, xfbml: true);
FB.login(function(response) {
if (response.authResponse) {
location.href = '/auth/facebook'; // omniauth-facebook で生やした認証URLへ移動するだけ
} else {
console.log('User cancelled login or did not fully authorize.');
}
});
まぁ確かにイケますよ。3rd party cookie
を許可していれば。
問題は許可していない場合で、色々検討した結果、
その時のフローは以下のようにすると良さそうだということが分かりました。
- facebook javascript sdk の FB.login or FB.loginStatus のコールバックで signed_request を取得。
- サーバーに signed_request を送信。
- サーバー側でパースして code を取得。
- code から access_token を取得。
- access_token を使って FB graph の /me で自身の情報を取得。
- omniauth の OAuth がコールバックで受け取るかたちに整形して、認証。
- cookie 使えないので毎回 signed_request を送信して上記のフローで認証。
具体的にどうやるんだ
Railsと連携する想定のコードはこんな感じ。
色々調べて分かったけど、コードまで明示しているところがさっぱりなかったのでメモとして記録しておきます。(そんなときの qiita
だよね!!!)
FB.init(appId: FB_APP_ID, status: true, cookie: true, xfbml: true);
FB.login(function(response) {
if (response.authResponse) {
var uid = response.authResponse.userID;
var signed_request = response.authResponse.signedRequest;
// /users/:id にしたいがためにuid取得してる
var url = 'http://example.com/users/' + uid;
// ここで access_token 取れるけどそれを生でサーバーに送っちゃダメよ
$.ajax({
url: url,
method: 'post',
data: {
signed_request: signed_request,
_method: 'put' # rails の PUT
},
success: function() {
// 認証後の何か
}
});
} else {
}
});
class UsersController
# これ微妙と思うなら、form用意しておいて、そこから $("input[name='authenticity_token']").val() して渡すと良い
skip_before_filter :verify_authenticity_token
def update
# FBアプリの credential は figaro gem で管理している想定
# FB api は Koala gem で操作する想定
oauth = Koala::Facebook::OAuth.new(Figaro.env.fb_app_id, Figaro.env.fb_app_secret)
# signed_request をパース
res = oauth.parse_signed_request(params[:signed_request])
# access_token を取得
access_token = oauth.get_access_token(res['code'])
# sigend_request を送ってきた奴の情報を取得
graph = Koala::Facebook::API.new(access_token)
user_info = graph.get_object('me?locale=ja_JP')
# omniauth で /auth/facebook/callback が受け取るenv['omniauth.auth'] のhash形式
# https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema に変換
auth =
{
'uid' => uid,
'info' => {
'name' => user_info['name'],
'nickname' => user_info['name'],
'image' => "https://graph.facebook.com/#{uid}/picture",
'email' => user_info['email'],
},
'credentials' => {
'token' => access_token,
},
}
# authを使ってユーザー作成 or 認証
User.create_or_udpate_by_auth(auth)
head :ok
end
end