2018年以降の記事はGitHub Pagesに移行しました

RubyでTwitterのOAuth認証をしてみる その2

前回のエントリ では、リクエストトークンまで発行してもらえました。だもんで今回は前回に続いてアクセストークンを発行してもらいます。アクセストークン発行の流れとしては、

  1. 今もらったリクエストトークンを付加して http://twitter.com/oauth/authorize へアクセスする
  2. 画面に表示されるPINコード(oauth_verifier)を控える
  3. リクエストトークンとPINコードをを付加して http://twitter.com/oauth/access_token へアクセスする

行きます。

PINコードを発行してもらう

リクエストトークンを発行してもらうと、

oauth_token=XXXXXXXXXX&oauth_token_secret=YYYYYYYYYYYYYYYYYYYYYYYYYYYY&oauth_callback_confirmed=true

という文字列がbodyに埋まってきたはず。*1

この中から oauth_tokenを抜き出し、 http://twitter.com/oauth/authorize にパラメータとしてくっつけます。こんな感じ。

http://twitter.com/oauth/authorize?oauth_token=XXXXXXXXXX

アドレスが正しければ、以下のような画面が出るはず。*2アプリケーションからのアクセスを許可しますか? と尋ねられるので許可するを選択。

許可すると、PINコードが画面に表示されるのでこれを控えておく。

再度signatureを作成する

このPINコードを"oauth_verify"として。先ほどURLに貼っつけたoauth_tokenを"oauth_token"としてパラメータとして持たせます。

そして、ここでもう一度signatureを作成を作成します。注意点としては、

  1. 今まで使っていたoauth_signatureは一旦消して、以下のパラメータで新たにつくり直す
    1. oauth_consumer_key
    2. oauth_nonce
    3. oauth_signature_method
    4. oauth_timestamp
    5. oauth_version
    6. oauth_token New!
    7. oauth_verifier New!
  2. URLは"http://twitter.com/oauth/access_token"をエスケープしたもの
  3. 暗号化用のキーは"consumer_secret&oauth_token_secret"*3

この3点でしょうか。
signatureが作成できたら、前回と同様にアルファベット順に並べて連結し、URLのおしりにくっつけます。URLはこんな感じになります。

成功したら、oauth_token, oauth_token_secret, user_id, screen_nameが返ってきます。これ俺や!!

ここで返してもらったoauth_tokenとoauth_token_secretは本物なので大切にとっておく! というわけで、次は自分のTimelineを取得します!

ソースはこんな感じ。

require 'openssl'
require 'uri'
require 'net/http'

# signature作成
def signature(method, consumer_secret, oauth_token_secret, url, oauth_header)
	# signature_keyの作成
	# リクエストトークン時は"CONSUMER_SECRET&"(アンドが入っている)
	# アクセストークン時は"CONSUMER_SECRET&OAUTH_TOKEN_SECRET"として使用
	signature_key = consumer_secret + "&"
	if !oauth_token_secret.nil? then
		signature_key += oauth_token_secret
	end

	# oauth_headerのパラメータをソートして連結
	param = sort_and_concat(oauth_header)

	# httpメソッドとURLとパラメータを&で連結する
	value = method + "&" + escape(url) + "&" + escape(param)
	# hmac_sha1
	sha1 = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, signature_key, value)
	# base64
	base64 = [sha1].pack('m').gsub(/\n/, '')
	return base64
end

# 文字列のエスケープ(: / = %をエスケープする。. _ -はそのまま)
def escape(value)
	URI.escape(value, Regexp.new("[^a-zA-Z0-9._-]"))
end

# oauth_headerの情報をアルファベット順に並べ替え & で結合
def sort_and_concat(oauth_header)
	oauth_header_array = oauth_header.sort
	param = ""
	oauth_header_array.each do |params|
		for i in 1..params.length
			param += params[i-1]
			if i % params.length == 0
				param += "&"
			else
				param += "="
			end
		end
	end
	param = param.slice(0, param.length-1)
end

# リクエストトークン取得用のURL
request_token_url = "http://twitter.com/oauth/request_token"
# PINコード取得用URL
authorize_url = "http://twitter.com/oauth/authorize"
# アクセストークン取得用のURL
access_token_url = "http://twitter.com/oauth/access_token"

# Twitterで登録したらもらえる
consumer_key = "XXXXXXXXXXXXXXXXXXXXX"
consumer_secret = "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"

# Twitterからもらえるアクセストークン(最初は使わない)
oauth_token = ""
oauth_token_secret = ""

# oauthパラメータたち
oauth_header = {
	# Consumer Key
	"oauth_consumer_key" => consumer_key,
	# 一意な値(今回は適当に実装)
	"oauth_nonce" => "AAAAAAAA",
	# 署名方式(HMAC-SHA1)
	"oauth_signature_method" => "HMAC-SHA1",
	# リクエスト生成時のタイムスタンプ(ミリ秒)
	"oauth_timestamp" => Time.now.to_i.to_s,
	# バージョン(1.0)
	"oauth_version" => "1.0",
}

# signature作成
oauth_header["oauth_signature"] = signature("GET", 
					consumer_secret, 
					nil, 
					request_token_url, 
					oauth_header)

# GETする
uri = URI.parse(request_token_url)
proxy_class = Net::HTTP::Proxy(ARGV[0], 8080)
http = proxy_class.new(uri.host)
http.start do |http|
	# oauth_headerのパラメータをソートして連結
	param = sort_and_concat(oauth_header)

	res = http.get(uri.path + "?#{param}")

	if res.code == "200" then
		# 返ってきた値を分割
		params = res.body.split("&")
		params.each do |param|
			# さらに=で分割し前部分をkey、後方部分をvalueに格納
			key,value = param.split("=")

			# リクエストトークンを格納
			if ("oauth_token" == key) then
				oauth_token = value
			elsif ("oauth_token_secret" == key) then
				oauth_token_secret = value
			end
		end

		# プロンプトにPINコード取得用URL表示
		print "#{authorize_url}?oauth_token=#{oauth_token}\n"
		print "Input PIN Code. Input...\n"

		# PINコード入力待ち
		oauth_verifier = STDIN.gets
		# 改行コード(\n)取り除き
		oauth_verifier = oauth_verifier.slice(0, oauth_verifier.length-1)

		# ヘッダにアクセストークンとPINコード追加
		oauth_header["oauth_token"] = oauth_token
		oauth_header["oauth_verifier"] = oauth_verifier
		
		# いったんoauth_signature削除
		oauth_header.delete("oauth_signature")

		# 再びsignature作成
		oauth_header["oauth_signature"] = signature("GET", 
							consumer_secret, 
							oauth_token_secret, 
							access_token_url, 
							oauth_header)

		# oauth_headerのパラメータをソートして連結
		param = sort_and_concat(oauth_header)

		# GETする
		uri = URI.parse(access_token_url)
		proxy_class = Net::HTTP::Proxy(ARGV[0], 8080)
		http = proxy_class.new(uri.host)
		http.start do |http|
			res = http.get(uri.path + "?#{param}")
			if res.code =="200" then
				print "#{res.code}\n"
				print "#{res.body}\n"
			else
				print "ERROR: #{res.code}\n"
			end
		end
	else
		print "ERROR: #{res.code}\n"
	end
end

処理の流れとしては、oauth_tokenを貼りつけたhttp://twitter.com/oauth/authorizeのアドレスを出力するので、(お手数ですが)ブラウザからアクセスしてもらう。

ブラウザに表示されたPINコードを貼っつけてもらって、これをoauth_verifierとし、アクセストークンを発行してもらう。

PINコードの所はいちいちブラウザから行かないようにすることもできそうなんですが……。

*1:この時のoauth_tokenとoauth_token_secretは一時的なものらしいので、アクセストークンがもらえれば忘れても良いようです。

*2:Twitterにログインしていなければ、ログイン画面がまず出たはず。

*3:リクエストトークンを作るときは"consumer_secret&"でした