OAuth認証

以前書いたものが一部間違えていたりと酷かったので、整理して書き直してみました。
それでも汚いし、間違いもありそうだけど。
調べれば図とかで解説してくれているところもあるので、そこを見るのもいいのです。
今回は、OAuth認証を通過するために実装する面で役に立てると思う話を書いてみます。(これも調べると沢山でます)



まず、仕様をざっと見るのがいいです。
僕は英語は読めませんが、ここを見ればどういう値を渡せばよいかぐらいは判断できます。


OAuth Core 1.0 Revision A
http://oauth.net/core/1.0a/


今回はTwitterを例にとって見ます。
APIドキュメントも公開されていますので、Twitter APIを利用する場合は参照することになるでしょう。
http://dev.twitter.com/doc#OAuth

手順の確認

OAuth認証を通過するためには、3回ほどやり取りしないといけません(その間もOAuthで決められた情報をやり取り)

  1. RequestTokenの取得
  2. RequestTokenからVerifierコードを取得
  3. Verifierコードを使ってAccessTokenの取得

AccessTokenを取得できれば、以後AccessTokenを使ってサービスを利用することができます。

認証方法

HTTPリクエストヘッダのAuthorizationにOAuth認証に必要な情報を与えるだけです。
必須なのが以下。

oauth_consumer_key ... アプリケーション固有のキー。アプリケーション作成の申請をした時に与えられる
oauth_signature ... 署名。
oauth_signature_method ... oauth_signatureの生成方式。Twitterでは"HMAC-SHA1"
oauth_version ... バージョン情報。普通は"1.0"
oauth_timestamp ... リクエスト時のエポック秒を与える
oauth_nonce ... リクエスト毎にランダムな文字列

oauth_nonceは本当に適当な文字列であればいいらしく、rand関数なりで適当な長さのものを作ってもOKでした。
しかし、過去に誰かが使っていると無効になってしまいます。


以下は最初は必要ないけど、最終的に必要なもの。

oautn_token ... ( リクエスト | アクセス )トークン。最初は持っていない。


後のパラメータは普通のクエリ情報として与えていきます。

signatureの作成

http://oauth.net/core/1.0a/#signing_process
まず、oauth_signature(署名)となるメッセージを生成する必要があります。
かなり重要で、処理がわかりにくいです。

メッセージの生成

メッセージの生成手順も決まっています。

  • HTTPリクエストメソッド(GET,POST)
  • URL
  • パラメータ

これらを、パーセントエンコーディングしてから、"&"で結合したものがメッセージになります。
URLには、URLに付くクエリ情報は含みません。
パラメータには、OAuth認証で使用する情報と、URLにくっついているクエリと、POSTするときのクエリも含みます。
当然ですが、OAuth認証で使用する情報にあるoauth_signatureは現在算出中なので含みません。
さらに、パラメータはkey=value形式であり、keyが昇順でソートされている必要があります。
パラメータがa=100&c=200&b=300とあったなら、a=100&b=300&c=200となります。

パーセントエンコーディング

パーセントエンコーディングすべき文字も決まっています。 => http://oauth.net/core/1.0a/#encoding_parameters
アルファベット, 数字, '-', '.', '_', '~'以外は全てパーセントエンコーディングします。
パーセントエンコーディングとは文字コードを16進数にしたものの先頭に%をつけます。
半角スペースは0x20なので"%20"と言う感じに。
Perlで書くと、こんな感じになります。

s{[^a-zA-Z0-9\_\-\~\.]}{sprintf('%%%02X', ord($&))}eg;
メッセージから署名を生成

署名の生成は、oauth_signature_methodと同じ方式を使います。
TwitterではHMAC-SHA1です。HMAC-SHA1については過去にちょろっと書きました。
http://d.hatena.ne.jp/ryousanngata/20101206


HMACで、キーとなるデータは、counsumer_secretとoauth_token_secretです。
counsumer_secretがABC、oauth_token_secretがDEFとした場合、それらを"&"で結合した"ABC&DEF"がキーになります。
counsumer_secretは、アプリケーション申請時に必ずもらえますが、
oauth_token_secretは、RequestTokenを要求するときにはまだ持ってません。
そのときは、"ABC&"と、oauth_token_secretだけ含まない形になります。


このキーを使ってメッセージを署名したものをパラメータエンコーディングしたものがoauth_signatureになります。

OAuth認証ヘッダの例

実際はカンマの後ろに改行はありません。
Twitterの場合、value部分はダブルクォートで括っても括らなくてもいいようです。

request_token時

Authorization: OAuth realm="http://api.twitter.com",
oauth_consumer_key=XXXXXXXXXXXXXXXXXXXXX,
oauth_nonce=0rtCszii3w81grcTvfZ5xBas6IcL0Krf,
oauth_signature=lu4yWSSadIOmGoxwlrVvx2b6VZA%3D,
oauth_signature_method=HMAC-SHA1,
oauth_timestamp=1295101469,
oauth_version=1.0

access_token時

Authorization: OAuth realm="http://api.twitter.com",
oauth_consumer_key=XXXXXXXXXXXXXXXXXXXXX,
oauth_nonce=iuxe8BHhZLVosDmhXNODPMDxalLzKJGP,
oauth_signature=oI9E%2F5rGN2AxjhQen0sqpVgM23U%3D,
oauth_signature_method=HMAC-SHA1,
oauth_timestamp=1295101502,
oauth_token=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
oauth_version=1.0

クエリ

oauth_verifier=12345678

リクエストの発行

基本的にHTTPリクエストヘッダのAuthorizationヘッダへOAuth認証の情報を与えて、
Content-Type:application/x-www-form-urlencoded でリクエストを発行します。

RequestTokenの取得

http://dev.twitter.com/doc/post/oauth/request_token
上で生成した認証ヘッダを与えると、oauth_tokenとoauth_token_secretを返してくれます。
これはリクエストークンで、verifierコードを得るときに必要です。
AccessToken取得時にも、このoauth_tokenとoauth_token_secretを使います。

RequestTokenからVerifierコードを取得

http://dev.twitter.com/doc/get/oauth/authorize
実際に取得するのは、アプリケーション作成者の仕事ではなく、ユーザーの仕事です。
twitter.com/oauth/authorizeにアクセスするのですが、このURLにoauth_tokenをくっつけます。
なのでURLは以下のようになります。
twitter.com/oauth/authorize?oauth_token=oauth_tokenの値
このURLをユーザーにアクセスしてもらって、ユーザー自身で認証してもらう事になります。
この仕組みによってアプリケーション提供者にユーザはアカウントとパスワードを教えなくて済みます。
ユーザーはここにアクセスして、自分のアカウントで認証すると、
Twitterの場合は、数字で8桁ぐらいのコードが画面に表示されます。これがverifierコードです。
これをAccessTokenを取得する時に使用します。


また、WEBアプリケーションであれば、コールバックURLにより転送させることもでき、
コールバックURL先にverifierコードを付与して転送することで、ユーザーがverifierコードを入力する手間を省く事ができます。


余談ですが、アカウントとパスワードを入力してもらってverifierコードを得る方法もあります。
例えばTwitterクライアントのTweetDeckはそうです。
こっちのほうが、いちいちユーザーにverifierコードを取得してきてもらって入力してもらう必要がないので、手間がかからずラクでしょうけど、
パスワードを入力するのが嫌なユーザーもいるでしょうし、実装するなら両方できるほうがいいと思います。
悪意のあるアプリケーション作成者はこっそりパスワードを自分に転送したりしているかもしれません;-)

Verifierコードを使ってAccessTokenの取得

http://dev.twitter.com/doc/post/oauth/access_token
oauth_verifier=Verifierコード をクエリに与えて、OAuth認証ヘッダをくっつけて、POSTするだけです。
ここはGETではダメです。
成功すると、oauth_tokenとoauth_token_secretを返してくれます。
これがAccessTokenになります。認証はコレでおしまいです。
後は、ローカルファイルなりに書き込んでおけば、
そのアプリケーションは以後、記録したAccessTokenを使ってサービスを利用できます。

AccessTokenの使い方。

今までの認証どおりにやっていきます。
AccessTokenで得たoauth_tokenとoauth_token_secretがあるなら、認証は要りません。
必要なクエリを与えて、OAuth認証ヘッダを生成し、好きなAPIにリクエストを発行していくだけです。
簡単でしょ?
あ、リクエストを発行する場合はHTTPSプロトコル(SSL)を使ったほうがいいです。
secret情報がばれない限りは成りすまされないでしょうが、最初からHTTPで通信していたら、oauth_token_secretもダダ漏れですし、内容も見え見えですし。

余談

ConsumerKeyとConsumerSecretがバレたら、なりすましアプリケーションが出来ちゃうんじゃないんだろうか?
1セットのConsumerKeyとConsumerSecretがあれば、複数のアプリケーションを作成できるわけですし。
WEBアプリケーションは流出しなければ問題ないけど、
TwitterClientとかインストールの必要があるものは、ConsumerKeyなどはどうやって扱ってるんだろうか。
やはり暗号化とかやってるのかなぁ。

ごみ

C++だけで書こうと思ったけど、Qtが便利すぎて辛いのでQtに依存したOAuthクラス+nを書きました。
参考になるかどうかチラ見したり汚いコードを見て萎えたりすればいいと思います。
既存のものを使ったほうがいいよ。
http://rying.net/arc/oauth_qt.zip
使い方は全く書いてないので簡単に書いておきます。

	oreq = new OAuthRequest(CONSUMER_KEY, CONSUMER_SECRET);
	oreq->setRequestTokenUrl("https://twitter.com/oauth/request_token");
	oreq->setAuthorizeUrl("https://twitter.com/oauth/authorize");
	oreq->setAccessTokenUrl("https://twitter.com/oauth/access_token");
if(/*過去にAccessTokenを取得しているなら*/){
	oreq->setOAuthToken(OAuthToken);
	oreq->setOAuthTokenSecret(OAuthTokenSecret);
}
	connect(oreq,SIGNAL(replyFinished(QNetworkReply*)), this, SLOT(なんとか(QNetworkReply*)));
	connect(oreq,SIGNAL(authorizeRequest(QUrl)), this, SLOT(かんとか(QUrl)));
	oreq->authorize(); // 認証♂開始

ユーザーがauthorizeへアクセスする必要が出たとき、シグナルでauthorizeRequest(QUrl)が送出されるので、
MainWindowなりでslotを作ってconnectで繋いでおけば、認証コード入力ダイアログを出す事ができます。
それ以外は、authorizeが認証作業を自動的にやってくれます。


GET/POSTリクエストはこんな感じで出来ます。queryは無くてもいいです。

QMap<QByteArray,QByteArray> query;
query["status"]="うんち";

oreq->get(URL1);
oreq->post(URL2, query);

リクエスト発行後すぐ戻ってきます。
リクエストの結果はやはりシグナルでreplyFinished(QNetworkReply *reply)が送出されるので、
slotを作ってその中でreply->readAll()とでもして、connectしておけば、リクエスト結果を受け取れます。
UserStreamみたいな場合にreplyFinishedが送出されるかは試してないです。