Facebook Signed Request を Ruby on Rails で扱う

最近いろいろな事情があり、Facebookアプリなるものを作ったりしているのですが、「Facebookページタブ埋め込み型のアプリ」を制作するに当たり、避けては通れない「Facebook Signed Request」の扱い方をまとめてみました。

Signed Requestとは?

Facebook Signed Request は、Webアプリを「Facebookアプリ」として動作させた際にアプリへのパラメータとして渡される各種情報のことです。

Signed Requestを利用すると、アプリ側で以下のような情報を得ることが出来ます。

  • タブとして埋め込まれている「元のFacebookページ」のIDを取得出来る。
  • アプリを表示しているユーザが、「元のFacebookページ」の管理者かどうかを取得出来る。
  • アプリを表示しているユーザが、「元のFacebookページ」の「いいね!」を押しているかどうかを判定出来る。

参考にしたURL:
ログインフローを手作業で構築する - Facebookログイン - ドキュメンテーション - 開発者向けFacebook
json - Decoding Facebook's signed request in Ruby/Sinatra - Stack Overflow

Base64デコード

Signed RequestはBase64エンコードされている為、デコードする為のメソッドを用意します。

def base64_url_decode(str)
  encoded_str = str.gsub('-','+').gsub('_','/')
  encoded_str += '=' while !(encoded_str.size % 4).zero?
  Base64.decode64(encoded_str)
end

Signed Request本体をBase64デコードして、JSONをHashに変換

Signed Request全体のうち、ドットで区切られた前半がsignature、後半がリクエスト本体です。
今回はRails環境なので、ActiveSupport::JSONを使ってサクッとHashに変換します。

def decode_data(str)
  encoded_sig, payload = str.split('.')
  data = ActiveSupport::JSON.decode(base64_url_decode(payload))
end

Signed Requestの妥当性チェック

リクエスト本体から生成したダイジェストが、signatureと等しいことをチェックします。ダイジェスト生成時のキーには、Facebookアプリの設定画面に表示されている「App secret」を使用します。
ダイジェストの生成にはruby-hmac gem を使っています。
(OpenSSL::HMACが使える環境であればそちらのほうがベターかと思いますが、OpenSSLのバージョンが古い環境だと、SHA256でのダイジェスト生成が出来ない可能性もあるので注意。)

APP_SECRET = "[Facebookアプリのsecret]"
def verify_signature(str)
  encoded_sig, payload = str.split('.')
  sig = base64_url_decode(encoded_sig)
  expected_sig = HMAC::SHA256.digest(APP_SECRET, payload)
  sig == expected_sig
end

Signed Requestの取得

アプリ側から見ると「params[:signed_request]」として取得出来る為、妥当性チェックの後にJSONとして取得します。

def signed_request
  if params[:signed_request] && verify_signature(params[:signed_request])
    decode_data(params[:signed_request])
  else
    nil
  end
end

Signed Requestの使い方

取得したSigned Requestは、以下のように使います。

# 埋め込まれているFacebookページのIDは?
signed_request["page"]["id"] # => '1111111111111' 

# いいね!が押されているか?
signed_request["page"]["liked"] # => true

# ページ管理者か? 
signed_request["page"]["admin"] # => true

注意事項

  • params[:signed_request]がアプリに渡されるのは、アプリ設定の「キャンバスページURL」で指定したURLに対してのみです。
    そのため、キャンバスページが表示される最初のタイミングで、必要に応じてセッションに格納するなどの処置が必要です。
  • ところが、上記の流れでセッションを使うと、IE環境でセッションが保持されないという問題が発生するはず。
    これは、IEのデフォルトではiframe内で別ドメインのサイトを表示した場合、Cookieが有効にならないために発生する現象とのことです。
    そんなときは、以下のURLを参考に対処します。(もちろん意味を理解した上でね!)
    P3Pコンパクトポリシーをコピペするのが流行らないことを祈る | 水無月ばけらのえび日記
class ApplicationController < ActionController::Base
  ...
  def ie_p3p_fix
   if request.env["HTTP_USER_AGENT"] =~ /MSIE/
      response.headers["P3P"] = 'CP="CAO PSA OUR"'
   end
  end
  before_filter :ie_p3p_fix
  ...