OAuth認証でmultipart/form-dataをPOST

oauth gemでマルチパートのPOSTは単体ではできないようなので、multipart-postと組み合わせる方法と、OAuthプロバイダ実装時の注意点について。

各gemは以下のコマンドでインストールできる。

$ sudo gem install oauth multipart-post

ポイントはマルチパートリクエストにOAuth::AccessToken#sign!を適用すること。

require 'oauth/consumer'
require 'net/http/post/multipart'

OAuth::VERSION = 1.0

consumer = OAuth::Consumer.new('consumer_key', 'consumer_secret',
                               :site => 'http://provider.example.com',
                               :request_token_path => '/request_token',
                               :authorize_path => '/authorize',
                               :access_token_path => '/access.token')

request_token = consumer.get_request_token(:oauth_callback => 'http://consumer.example.com/callback')

puts request_token.authorize_url

access_token = request_token.get_access_token

File.open("./image.jpg") do |image|
  req = Net::HTTP::Post::Multipart.new(url.path,
                                       'text' => 'hi, there',
                                       'image' => UploadIO.new(image, "image/jpeg", "image.jpg"))
  access_token.sign! req
  res = Net::HTTP.start('provider.example.com', 80) do |http|
    http.request(req)
  end
end

僕はOAuthプロバイダを実装していて、そのテストスクリプトとして上記を実行している。
その際に注意が必要だった点として、マルチパートの各フィールドはSignature Base Stringに含めてはならないことが挙げられる。
上記のスクリプトは正しくtextやimageをSignature Base Stringに含めないが、プロバイダサイドで通常のリクエストとマルチパートリクエストを抽象化して扱っていたため、textを含むSignature Base Stringを生成していた。
そのため、マルチパートリクエストのSignatureが一致しないため401が返るという問題が起きる。

この問題を含むSignatureに関連したバグの調査にはコンシューマとプロバイダのSignature Base Stringを比較する必要があるが、oauth gem側では簡単にSignature Base Stringを取り出す方法がないかもしれない。
(以下の方法を試したが、出力内容は純粋なSignature Base Stringではない)

  res = Net::HTTP.start('provider.example.com', 80) do |http|
    puts req.signature_base_string(http, consumer, access_token)
    http.request(req)
  end

また、oauth gemはRFC 5849 - The OAuth 1.0 Protocolで定義されないoauth_body_hashというパラメタを付加する。これはOAuth Request Body Hashという拡張のようだ。http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
これもプロバイダ側でSignature Base Stringに含める必要がある。

まとめ1:oauth gem + multipart-post = うごく
まとめ2:OAuthプロバイダはSignature Base Stringにマルチパートのフィールドを含めるな&コンシューマの拡張に寛容に