Mixiのライフサイクルイベントの署名付きリクエスト / Mixi Lifecycle Event OAuth signatures

Mixiのライフサイクルイベント(アプリが追加された、アプリがマイアプリから削除されたといったイベント)の署名付きリクエストは他のよりまた少し違うみたいです。公開鍵を使うところは他にもありますが、ライフサイクルイベントの場合は、OAuth情報がHTTPのAuthorizationヘッダーに入っているため、お使いのOAuthライブラリーによってうまくいかない場合があるかもしれません。Rubyのoauthではほぼ動くのですが、

xoauth_signature_publickey=lc_20131107

が署名に含まれないため失敗します。

signature = OAuth::Signature.build(request, {:parameters => {'xoauth_signature_publickey' => 'lc_20131107'}}) do

のようにOAuth::Signature.build()に渡すと署名に含まれ、署名の検証が通ります。

検証が通ったbase stringの例:

GET&http%3A%2F%2FXXXXXXXXXX%2FXXXXXXXXXX%2FXXXXXXXXXX%2Faddapp&eventtype%3Devent.addapp%26id%3Dmo3XXXXXXX7fr%26mixi_invite_from%3DmgwXXXXXXXnt8%26oauth_consumer_key%3Dmixi.jp%26oauth_nonce%3D719445958eb7ae359824%26oauth_signature_method%3DRSA-SHA1%26oauth_timestamp%3D1468335606%26oauth_version%3D1.0%26opensocial_app_id%3D41345%26xoauth_signature_publickey%3Dlc_20131107

ちなみに、xoauth_signature_publickeyが一生変わらないわけではありません。Mixiの公開鍵と同じタイミングで変わる予定です。現在使われている公開鍵の有効期限は確か2020年だったと思いますので、まだしばらく大丈夫かもしれないですね。

英訳の下のmixi_signed_request?関数の例もご参照ください。

Mixi’s Lifecycle events (add app and remove app) use an OAuth scheme that is slightly different from Mixi’s other OAuth implementations: the OAuth headers are included in the HTTP Authorization header. Depending on your OAuth library,  the non-standard xoauth_signature_publickey (passed in the HTTP header) may not be included when calculating the signature. However, Mixi includes this parameter. You’ll have to pass it manually to OAuth::Signature.build(), e.g., like this:

signature = OAuth::Signature.build(request, {:parameters => {'xoauth_signature_publickey' => 'lc_20131107'}}) do

Here’s an example of a base string that could pass validation:

GET&http%3A%2F%2FXXXXXXXXXX%2FXXXXXXXXXX%2FXXXXXXXXXX%2Faddapp&eventtype%3Devent.addapp%26id%3Dmo3XXXXXXX7fr%26mixi_invite_from%3DmgwXXXXXXXnt8%26oauth_consumer_key%3Dmixi.jp%26oauth_nonce%3D719445958eb7ae359824%26oauth_signature_method%3DRSA-SHA1%26oauth_timestamp%3D1468335606%26oauth_version%3D1.0%26opensocial_app_id%3D41345%26xoauth_signature_publickey%3Dlc_20131107

Note that the certificate and the “lc_20131107” are linked. When there is a change, both will be updated. The current certificate is valid until 2020 or so.

The full verification code could look like this:

  def mixi_signed_request?
    mixi_certificate = <<END
-----BEGIN CERTIFICATE-----
MIIDNzCCAh+gAwIBAgIJAIQ3zDiILtpzMA0GCSqGSIb3DQEBBQUAMDIxCzAJBgNV
BAYTAkpQMREwDwYDVQQKDAhtaXhpIEluYzEQMA4GA1UEAwwHbWl4aS5qcDAeFw0x
MzExMDcwODQwNTBaFw0yMzExMDUwODQwNTBaMDIxCzAJBgNVBAYTAkpQMREwDwYD
VQQKDAhtaXhpIEluYzEQMA4GA1UEAwwHbWl4aS5qcDCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAMxzCu9sUctGAzL/X0/sH2MSRmc/+X2Wx87ObZDpEd5P
19mIUQXW6hCXObB3SkE7kMuXiRhtrxwsnB9fjYIUEq/1vsTHkLJoJVUFIumqe6EH
c/WZaTmu34WpEUFXNDS4htidXyVqikoDQZF9wdczyH7bLPbekQfRAcyek3E6/7Qi
B00yWUqK8FcUOD4ILmtSHXsz4BNqekNgEzfUi5WkBYKtuD5zSunZalbWUPS7xa57
o1auVdclaHBfqe8dC5DTbxIe0szpHckQrJF9fJ/bIQSmvY6ADBRGfoLF7Fgoc5x+
R5my9weytzg4WdDUjYrxmhy5IpjxytipQqrFDqAUxl8CAwEAAaNQME4wHQYDVR0O
BBYEFKCQSlssCWLqd0tT7NVtoBUNzCasMB8GA1UdIwQYMBaAFKCQSlssCWLqd0tT
7NVtoBUNzCasMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBALoI7elk
ZCv+pUpi6aJepzLnQDYHB2eXpDkpWEUrF1WMvx8ovmWdVeviHqUdFtGL0XZ5tSoV
vGE/KQTavag+MfbafKaff4iHXNyNMygeFP7r/FaFQQRafyNfhaXF6sWfwKrOk/Bc
jIXFN9tYWN6LEwNgYT0C+OSOppQJzt2y1am15FExAHQcIEFYc+3T+MGGJ7e9H8tn
Qz84WmIgNZRUMYQC0PJTsMNVvr+/DTIzjabKz4W8qodXGA7AxXiRYdgC+3RUj/rA
lR09PXZ6nRaKiB0KBDIMUlPu/0u0Vw+GBt0ckH0htOKSxsn09jkITFhy3NX7Slbk
jlnY4pS9JO+avmM=
-----END CERTIFICATE-----
END

    require 'oauth'
    require 'oauth/signature/rsa/sha1'
    begin
      consumer = OAuth::Consumer.new(nil, mixi_certificate, {:signature_method => 'RSA-SHA1'})
      signature = OAuth::Signature.build(request, {:parameters => {'xoauth_signature_publickey' => 'lc_20131107'}}) do
        [nil, consumer.secret]
      end
      return signature.verify
    rescue => e
      logger.debug(e.inspect)
      return false
    end
  end