PRESSMAN*Tech

HTML5, CSS3からWordPress, AWS, Elasticsearchまで。プレスマンのスタッフが綴る技術ブログ。

OAuthをPHPでイチから書いてみた(その2)

その1で認証画面を表示するところまでたどり着きました。この時点ではリクエストトークンは未認証の状態です。
まあ、認証画面でこれから認証してもらうわけですし当たり前ですね。
それでは「アプリを認証」ボタンをポチッとな・・・・・・・。
コールバックURLに設定したURLにリダイレクトされました。コールバックURLにoobを設定していた場合はPINコードが出ます。

コールバックURLにリダイレクトすると以下のようなパラメーターがURLに付いてくるはずです。これがユーザーに認証されたリクエストトークンとなります。これを使用してアクセストークンを取得していきます。


さて、ここでちょっと前回のプログラムを見直してみます。
前回はGETメソッドでリクエストトークンを取得それを認証用ページのURLにくっつけてアクセスしたわけですが、大分手作業が入ってしまっています。
この記事に趣旨はOAuthが何をやっているか理解するためなのでそれでもいいのですが、もう少しだけ自動化してみます。

まず、プログラムの中で作成したリクエストトークンを要求するためのURLを使ってサーバーからリクエストトークンを取得する部分を自動化します。
GETメソッドでこのまま行ってもいいのですがtwitterのOAuthのドキュメントでもPOSTを使うべきと書かれているので折角なのでPOSTメソッドで行います。

まず、POSTなのでいままでGETで渡していたパラメーターはそのままでは渡せません。しかし、POSTとしてデータで渡すのではなくHTTPヘッダに埋め込んで渡します。
その後にサーバーと通信をするのですが、それにはいくつかやり方があります。今回はcURLを使用してみます。
ただし、PHPのコンパイル時にcurlを有効にしておくことが必要ですのでストリーム通信でのやり方も記述しておきます。

(0) 今回はPOSTなのでGETからPOSTに変える。

(1)リクエストトークンを取得するためのHTTPヘッダを作成

GETでリクエストトークンを取得したときに作成したパラメータと内容は同じです。
すこし形が変わっていて、以下のように。OAuthと先頭についてその後パラメータをカンマで結合して値をダブルクォーテーションで囲む形になします。

これをAuthorization:としてHTTPヘッダに埋め込みます。

(2)curlを使用してPOSTでリクエストトークンを取得。

curlはとても便利で最初に通信するためのオプションを設定して通信するだけです。
その中のいくつかを説明しておきます。

curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); //(a)
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
'Authorization :' . $oauthHeader,
'Content-Length:',
'Expect:',
'Content-Type:')
);//(b)
curl_setopt($curl, CURLOPT_POST, true);//(c)

レスポンスを画面に出力しないでcurl_exec()の返り値とする。

(a) HTTPヘッダをセット。これがちょいとキモになります。Content-Length、Expectの二つのヘッダが問題になります。
(b) 通信時に送信しているヘッダはCURLINFO_HEADER_OUTをcurl_setoptでtrueにしてcurl_getinfo()で取得できるのですが、これで取得してみると
Content-Length: -1
Expect: 100-continue
が自動的に付加されています。(環境依存かもしれませんが・・・)
そうすると、twitterのサーバーがエラーを返してくるのでこれらを消すようにしてあります。
(c) POSTで送信するのでPOSTに設定します。

これで問題が無ければリクエストトークンを取得できます。

(3)curlが使えない場合のためのストリームでの通信

特にこれと言って難しいことも無いというかcurlを使うより簡単だったりします。ただ、他の部分もそうですが通信エラー時の処理などは何も実装していませんので注意。

(4)リクエストトークンはこの後の処理で必要になるのでセッションに格納しておく。

(5)リダイレクトして認証画面を表示する。

これで、このプログラムがあるURLにアクセスするだけで認証画面が出るようになりました。cURLでの通信はこの後アクセストークンを取得するときやユーザーのtwitterデータを取得するときに使えます。
認証用ヘッダを作る部分も含めてほぼ同じ処理になるので、クラスでも作ってユーザーオブジェクトにしてその中に通信メソッドなんかを実装、オブジェクトをセッションで保持しておくなどいろいろと実装の仕方はありますが、これはあくまでもOAuthの流れを見るのが目的ですので次回以降はコピペプログラムっぽくなっていきます。
実際に使う場合はOAuth用のライブラリを使う方が簡単ですしね。以下ソースです。

<?php
$oauthConfig = array(
'callbackUrl' => 'http://net.pressmantech.com/twitter/accesskey.php',
'authorizeUrl' => 'https://api.twitter.com/oauth/authorize',
'requestTokenUrl' => 'https://api.twitter.com/oauth/request_token',
'accessTokenUrl' => 'https://api.twitter.com/oauth/access_token',
'consumerKey' => 'pD4dm6IQHa6jhtge82Fg',
'consumerSecret' => 'BfTtokF9bfb5Fim6BsPaEg9Fo3y4FM6kPlCQQiRPKA4'
);
$method = 'POST'; //(0)
$nonce =  md5(uniqid(rand(), true));
$timestamp = time();
$authorization = array(
'oauth_nonce' => $nonce,
'oauth_signature_method' => 'HMAC-SHA1',
'oauth_timestamp' => $timestamp,
'oauth_consumer_key' => $oauthConfig['consumerKey'],
'oauth_version' => '1.0'
);
if($oauthConfig['callbackUrl']){
$authorization['oauth_callback'] = $oauthConfig['callbackUrl'];
}else{
$authorization['oauth_callback'] = 'oob';
}
ksort($authorization);
$signatureBaseString = '';
foreach($authorization as $key => $val){
$signatureBaseString .= $key . '=' . rawurlencode($val) . '&';
}
$signatureBaseString = substr($signatureBaseString,0,-1);
$signatureBaseString = $method . '&' .
rawurlencode($oauthConfig['requestTokenUrl']) . '&' .
rawurlencode($signatureBaseString);
$signingKey = rawurlencode($oauthConfig['consumerSecret']) . '&';
$authorization['oauth_signature'] =
base64_encode(hash_hmac('sha1',$signatureBaseString,$signingKey,true));
//↑ここまではGETがPOSTになる以外は前回と同じ---------------------------------
/**
* (1)リクエストトークンを取得するためのhttpヘッダを作成
* GETで取得したときとほぼ同じだが、値がダブルクォーテーションで囲まれて、
* カンマで区切られている
*/
$oauthHeader = 'OAuth ';
foreach($authorization as $key => $val){
$oauthHeader .= $key . '="' . rawurlencode($val) .'",';
}
$oauthHeader = substr($oauthHeader,0,-1);
/**
* (2)curlを使用してPOSTでリクエストトークンを取得。
* POSTで先ほど作成したヘッダで通信しているだけだが、デフォルト設定のままだと
* Content-Length: -1,Expect: 100-continueをヘッダにつけることがあり、
* そうするとtwitterのサーバーが413 Request Entity Too Large,
* 417 Expectation Failedのエラーをはくので二つのヘッダは消してある
*/
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $oauthConfig['requestTokenUrl']);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curl, CURLOPT_TIMEOUT, 30);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
'Authorization :' . $oauthHeader,
'Content-Length:',
'Expect:',
'Content-Type:')
);
curl_setopt($curl, CURLOPT_POST, true);
$response = curl_exec($curl);
/**
* (3)curlが使えない環境もあるので簡単に書いたストリームで接続するソースも
* つけておく。
*/
/*
$fp = stream_socket_client("ssl://api.twitter.com:443", $errno, $errstr, 30);
fwrite($fp, "POST /oauth/request_token HTTP/1.0\r\nHost: api.twitter.com\r\n"
. "Accept: *"."/"."*\r\nAuthorization :" . $oauthHeader . "\r\n\r\n");
$response = '';
while (!feof($fp)) {
$response .= fgets($fp, 1024);
}
fclose($fp);
$response = substr($response,strpos($response,"\r\n\r\n") + 4);
*/
$requestToken = array();
foreach(explode('&',$response) as $v){
$param = explode('=',$v);
$requestToken[$param[0]] = $param[1];
}
/**
* (4)リクエストトークンはこの後の処理で必要なのでセッションに格納しておく。
*/
session_start();
$_SESSION['TWITTER_REQUEST_TOKEN'] = serialize($requestToken);
/**
* (5)リダイレクトして認証画面を表示する。
*/
$redirectUrl = $oauthConfig['authorizeUrl'] . '?oauth_token='
. $requestToken['oauth_token'];
header('location: ' . $redirectUrl);
投稿者:taichi

taichi の紹介

何でもやりますが、一応プログラマです。
このエントリーをはてなブックマークに追加

私たちと一緒に働きませんか?

株式会社プレスマンでは、プログラミングが大好きな方、仕事を通してさらにスキルを磨きたい方を募集しています。まずは募集職種をご覧の上、お気軽にお問い合わせください。あなたとお会いできるのを楽しみにしています。

プレスマンの採用情報を見る →

コメントは受け付けていません。

▲ 先頭へ戻る