TwitterのbotをOAuthに対応させる

Twitterの認証方式について

Twitterの認証方式にはOAuthとBasic認証の二通りの仕組みがあります。

Basic認証はお手軽なので、世に存在するTwitter APIを利用する一般的なクライアントは、大抵がこちらの方法を使っているのではないかと思います。

ですが、実はこのBasic認証はセキュリティ上の理由から、将来的に非推奨になることが予告されています。

既にTwitter API Wikiでは「今後作成するクライアントはOAuthを利用することを強く推奨する」と表明されており、それに応じてOAuthを使うクライアントも増えてきています。

…が、現状Basic認証を利用しているクライアントが既に大量に存在していることを考えると、今日明日でBasic認証が使えなくなる可能性はそう高くはないでしょう。先程のFAQでも、将来的に非推奨にすることは考えているが、日程は全くの未定である…と書いてあります。

何故OAuthを使いたいか

しかし実は、そういうセキュリティ的な事情以外にも、bot製作者的に「OAuthに移行したい!」と思わせる嬉しい利点がOAuthには用意されているのです。

それは投稿したTweetに表示される“クライアント名”を任意に設定できるということです。Web上でタイムラインを見たときに「webで」とか「Tweenで」とか表示されている部分ですね。

昔はBasic認証を利用するクライアントでもクライアント名を登録できたのですが、現在はOAuthへの移行を促すためか、OAuthを利用するクライアントしか登録することができません。その代わり、一人で複数のクライアントを登録できたり、いつでも好きなときに名前を変えることができたりと、かなり自由にクライアント名を弄ることができます。

これを使えば、例えばネタ系のbotなんかは、無機質な「APIで」という表示の変わりに、そのbot専用のお茶目な名前なんかを設定できたりしちゃうわけです。実際にこれを利用してユニークなクライアント名を表示しているbotもちらほら見かけますね。

ということで、セキュリティの向上というよりも*1むしろ、クライアント名を好きに弄ることを主目的として、botをOAuthに対応させる手順を書いていこうと思います。使用する言語はRubyです。

OAuthの仕組み

OAuthの仕組みについては以下のサイトなどの解説を参照してください。

認証の仕組みに馴染みが薄いと理解が難しい部分ですので、この記事では「使い方」以上のことは言及しません。

以下の記述でも、「クライアント=コンシューマ+ユーザ」くらいの大雑把な概念で用語を使用しています。

RubyでOAuthを利用する準備を整える

実際の手順に取りかかる前に、RubyでOAuthを利用する準備をします。

幸いruby-oauthというライブラリが存在していますので、ありがたく使わせてもらうことにしましょう。

$ gem install oauth

必要ならroot権限で実行してください。

これであとは

require 'rubygems'
require 'oauth'

すれば使えるわけですが、ここで一点注意しなければならないことがあります。

どうやらこのruby-oauthはRuby1.9系に完全には対応していないようで*2、そのまま使おうとすると例外が発生してしまいます。

とりあえずRuby1.9系でも最低限の機能だけは使えるようにするパッチを書いてみました。完全なものではありませんが、この記事に書いてあるレベルのことなら実行できるようになります。

oauth-patch.rb
if RUBY_VERSION >= '1.9.0'
  # エンコーディングの違いのせいで、
  # 日本語の文字列をpostパラメータに含めようとするとエラーが出ます。
  # 無理矢理エンコーディングをUTF-8に変えて再試行することで回避。
  module OAuth
    module Helper
      def escape(value)
        begin
          URI::escape(value.to_s, OAuth::RESERVED_CHARACTERS)
        rescue ArgumentError
          URI::escape(
            value.to_s.force_encoding(Encoding::UTF_8),
            OAuth::RESERVED_CHARACTERS
          )
        end
      end
    end
  end

  # 1.9から文字列がEnumerableでなくなりましたので、
  # その対応をしています。
  module HMAC
    class Base
      def set_key(key)
        key = @algorithm.digest(key) if key.size > @block_size
        key_xor_ipad = Array.new(@block_size, 0x36)
        key_xor_opad = Array.new(@block_size, 0x5c)
        key.bytes.each_with_index do |value, index|
          key_xor_ipad[index] ^= value
          key_xor_opad[index] ^= value
        end
        @key_xor_ipad = key_xor_ipad.pack('c*')
        @key_xor_opad = key_xor_opad.pack('c*')
        @md = @algorithm.new
        @initialized = true
      end
    end
  end
end

以降のサンプルプログラムでも、この"oauth-patch.rb"をrequireして利用しています。

OAuthクライアントを登録する

まず、以下のページにアクセスしてクライアントを登録します。

このとき、クライアント製作者のアカウントでWebからTwitterにログインした状態でアクセスしてください。作成したクライアントはTwitterのアカウントと紐付けられるためです。

"Register a new application"のリンクをクリックして登録画面へ進み、必要な情報を入力して「保存する」をクリックしてください。最低限"Application Name"、"Description"、"Application Website"が入力されていれば良いようです。

"Application Name"や"Description"には日本語も使えます。"Application Website"にはbotの説明ページなどがあればそのURLを、無ければ自分のサイトなど、製作者がわかるようなURLを入れておいたほうが良いでしょう。

それから、botから利用することを考えると

  • "Application Type"は"Client"に
  • "Default Access type"は"Read & Write"に

設定しておくことも必要でしょう。特に後者は"Read-only"のままにしておくと、そのクライアントを介したTweetの投稿などができなくなってしまうようです。

登録に成功すると"Application Details"の画面に移動します。そこに表示されている"Consumer key"と"Consumer secret"の値を使用しますので、メモって(コピペして)おいてください。

この"Application Details"画面には、先程の"Applications Using Twitter"画面からいつでもアクセスすることができます。また、クライアント名の変更などもこの画面から行えます。*3

アクセストークンを取得する

次に、このOAuthクライアントにbotアカウントでのアクセス許可を与え、アクセストークンを取得します。

この辺の話はOAuthの仕組みを知らないと少々ややこしいかもしれません。気になる方は先程のサイトなどでおさらいしてください。気にならない方は以下書いてある通りに実行すればOKです。

このアクセストークンを取得する手順も全自動で行えると良いのかもしれませんが、幸いTwitterはアクセストークンに有効期限を設定していないようですので、この手順を実行するのは最初の一回だけということになります。

ということであまり頑張らずに、一部手動を介して実行することにします。

次のような半自動スクリプトを書いてみました。

twitter-oauth.rb
#!/usr/bin/env ruby
# coding: utf-8

require 'rubygems'
require 'oauth'
require 'oauth-patch'

CONSUMER_KEY = 'CONSUMER-KEY' # ←ここを書き換える
CONSUMER_SECRET = 'CONSUMER-SECRET' # ←ここを書き換える

consumer = OAuth::Consumer.new(
  CONSUMER_KEY,
  CONSUMER_SECRET,
  :site => 'http://twitter.com'
)

request_token = consumer.get_request_token

puts "Access this URL and approve => #{request_token.authorize_url}"

print "Input OAuth Verifier: "
oauth_verifier = gets.chomp.strip

access_token = request_token.get_access_token(
  :oauth_verifier => oauth_verifier
)

puts "Access token: #{access_token.token}"
puts "Access token secret: #{access_token.secret}"

ソース中のCONSUMER-KEYとCONSUMER-SECRETを先程メモった値に書き換えて保存してください。

スクリプトを実行すると、以下のような出力が表示されます。REQUEST-TOKENの部分には実際にはもっと複雑な文字列が入っています。

$ ruby twitter-oauth.rb
Access this URL and approve => http://twitter.com/oauth/authorize?oauth_token=REQUEST-TOKEN
Input OAuth Verifier: 

'Access this URL and approve =>'の先に示されるURLに、botのアカウントでWebからTwitterにログインした状態でアクセスしてください。

"Allow (クライアント名) access?"、"拒否する"、"Allow"と表示されたページに移動すると思います。ページ内にクライアント名やその製作者名が表示されていますので、間違いが無いことを確認して"Allow"をクリックしてください。*4

"Allow"すると"You've successfully granted access to (クライアント名)!"と表示された画面に移動し、でかでかと7桁の*5数字が表示されると思います。

先程のスクリプトが入力待ちの状態で止まっていますので、この数字をそこに入力してください。

すると、最終的に以下のような出力が表示されてスクリプトが終了します。

Access token: ACCESS-TOKEN
Access token secret: ACCESS-TOKEN-SECRET

ACCESS-TOKEN、ACCESS-TOKEN-SECRETには実際にはもっと複雑な文字列が表示されます。この二つの値をメモって(コピペして)おいてください。

ここまでで準備は完了です。

アクセストークンを使ってTwitter APIを利用する

では、取得したアクセストークンを使ってTwitter APIを利用し、タイムラインの取得やTweetの投稿をするサンプルスクリプトを書いてみます。

twitter-oauth-access.rb
#!/usr/bin/env ruby
# coding: utf-8

require 'rubygems'
require 'oauth'
require 'oauth-patch'
require 'json'

CONSUMER_KEY = 'CONSUMER-KEY' # ←ここを書き換える
CONSUMER_SECRET = 'CONSUMER-SECRET' # ←ここを書き換える
ACCESS_TOKEN = 'ACCESS-TOKEN' # ←ここを書き換える
ACCESS_TOKEN_SECRET = 'ACCESS-TOKEN-SECRET' # ←ここを書き換える

# 下準備
consumer = OAuth::Consumer.new(
  CONSUMER_KEY,
  CONSUMER_SECRET,
  :site => 'http://twitter.com'
)
access_token = OAuth::AccessToken.new(
  consumer,
  ACCESS_TOKEN,
  ACCESS_TOKEN_SECRET
)

# タイムラインを取得して時系列順に表示
response = access_token.get('http://twitter.com/statuses/friends_timeline.json')
JSON.parse(response.body).reverse_each do |status|
  user = status['user']
  puts "#{user['name']}(#{user['screen_name']}): #{status['text']}"
end

# Tweetの投稿
response = access_token.post(
  'http://twitter.com/statuses/update.json',
  'status'=> 'このメッセージはOAuth認証を通して投稿しています。'
)

CONSUMER-KEY、CONSUMER-SECRET、ACCESS-TOKEN、ACCESS-TOKEN-SECRETを先程メモった値に書き換えて保存してください。日本語が含まれていますので、文字コードUTF-8にすることを忘れないでください。

スクリプトを実行すると、タイムラインの直近20件が表示され、また「このメッセージはOAuth認証を通して投稿しています。」というTweetが投稿されます。

$ ruby twitter-oauth-access.rb

投稿した内容は実際にWebにアクセスするなどして確認してみてください。「APIで」の部分が、先程登録したクライアント名で表示されていたら成功です。また、"Application Details"の画面からクライアント名を変更すると、タイムライン上の表示にも即座に反映されることがわかると思います。

上のソースを見てもらえればわかりますが、ユーザ名やパスワードなどの情報は一切保持していません。

アクセストークンなどの情報は保持していますが、このアクセストークンに対するアクセス許可は、ユーザが任意に取り消すことができます。

botのアカウントでTwitterの「設定」ページを開いてみてください。一番右に「このユーザーに対する操作」というタブが増え、そこにアクセスを許可したクライアントがリストアップされています。ここからいつでも「許可を取り消す」ことができます。

この辺りがOAuthがBasic認証よりもセキュリティ的に優れていると言われる理由なわけですね。*6

botスクリプトをアクセストークンを利用するように書き換える

さて、アクセストークンを使ったTwitter APIの利用方法がわかりましたので、あとはbotスクリプトを書き換えてやれば、いつでも好きなときにクライアント名を変更できるbotの完成です。

実際にどこをどう書き換えればいいのかは、利用しているライブラリに依存しますので一概には言えません。

一例を挙げますと、jugyo氏作のRubyTwitterライブラリ"Rubytter"は、予めOAuthを使ったアクセス方法が組み込まれていますので、上のサンプルスクリプトでも作成したOAuth::AccessTokenクラスのインスタンスさえあれば、すぐにでもOAuth対応させることができます。

OAuthに対応していないライブラリの場合は、Net::HTTPを呼び出している部分をOAuth::AccessTokenクラスのメソッドに書き換える必要があります。Net::HTTPとOAuth::AccessTokenのメソッド間対応は下のページなどを参考にしてください。シンプルに1対1対応していますので、あまり迷うこともないかと思います。

他に、コンシューマキーやアクセストークンキーなどの情報を渡す仕組みも用意してやる必要があるかもしれません。

クラッチから書き起こしている場合も、やはりNet::HTTP部分をOAuth::AccessTokenに置き換えてやればOKです。既存のライブラリを書き換えるよりは、こちらのほうが融通が利きますので楽かもしれませんね。

まとめ

長くなりましたが、要点としては

  1. クライアントを登録し
  2. 半自動スクリプトを使ってアクセストークンを取得し
  3. botスクリプトのNet::HTTP部分をOAuth::AccessTokenに置き換える

という手順でOK、ということになります。

スクリプトの作りによっては若干手間がかかるかもしれませんが、一回手順を覚えてしまえば、あとは流れ作業なのでカンタンです。手順1と手順2でログインするTwitterアカウントを切り替えないといけないのが注意点といえば注意点でしょうか。*7

以上です。OAuthを使って、是非愛するbotにユーモア溢れるクライアント名を付けてあげてください!*8

*1:もちろんそれも大事なのですが

*2:あるいは私の使い方が悪いだけなのかもしれませんが…

*3:"Edit Application Settings"ボタン

*4:余談ですが、他のクライアントに対してあまり考えずに"Allow"することは、セキュリティ上の重大な過失となり得ます。十分に注意してください。詳しくはOAuthを悪用したTwitter DMスパムが登場 - まちゅダイアリー(2009-08-01)などを。

*5:桁数は変動するかも

*6:Twitter以外のサービスにIDやパスワードを教える必要が無い、という意味で。botのように自分で管理しているものなら、その辺りはさほど関係ないかもしれません。

*7:クライアントとbotが1対1対応するなら、両方botのアカウントでも良いのかもしれません。

*8:あまり頻繁に設定を変更するとTwitterから怒られるかもしれませんので、ほどほどに!