Facebook、TwitterのOAuthを実装しよう第四弾

前回の続きです。
今回は投稿までのプログラム解説になります。

以前作ったフレームワークhttps://github.com/oyamatsu/api_framework)で開発しています。

目標

ライブラリを使わずにTwitter OAuth認証とツイートを投稿するAPIを作る

主要技術・環境

  • PHP
  • 俺式フレームワーク 名前はまだない

実装

構造のおさらい

 1. リクエストトークンを発行
 2. リクエストトークンを使ってアプリ認証画面に飛ばす
 3. コールバックURLでアクセストークンを取得
 4. アクセストークンを使ってAPIを呼ぶ

と、前回書きました。

 1. リクエストトークンを発行
 2. リクエストトークンを使ってアプリ認証画面に飛ばす

リクエストトークン発行からリダイレクトさせるまでが1プログラムです。

 3. コールバックURLでアクセストークンを取得

callback.phpでアクセストークンを取得、セッションに入れておいて、次のページに遷移。

 4. アクセストークンを使ってAPIを呼ぶ

セッションに入れたアクセストークンでツイートするAPIを呼ぶ。という流れになります。
気に入った。この流れに沿って説明してやる。

その前にシグネチャ生成

OAuthの流れに沿って説明すると約束したな、あれは嘘だ。
まずは全てのAPI通信のキモとなるシグネチャ生成について説明します。

要は、送るパラメータをURLエンコードしてくっつけていって、
sha1で暗号化してbase64_encodeかける、というだけになります。
正確には、RFC5849 – The OAuth 1.0 Protocolの決まりに沿って、下記を行う必要があります。

  • 特定文字をエンコードしない
Characters in the unreserved character set as defined by
[RFC3986], Section 2.3 (ALPHA, DIGIT, “-“, “.”, “_”, “~”) MUST
NOT be encoded.
  • スペース(%20)を+に置換
it encodes space characters as “%20″ and not using the “+” character

これをやらないと、アクセストークンは取得できるが、ツイート投稿のAPIを叩く時に、
不正シグネチャとされてOAuth認証が通っていないという401エラーが返ってきて混乱します。
ということでまとめると、下記のような関数が必要になります

private function build_signature($method, $url, $params, $consumer_secret, $token_secret = '') {
    ksort($params);
    $base = rawurlencode($method).'&'.rawurlencode($url).'&'.rawurlencode(str_replace( array( '+', '%7E' ), array( '%20', '~' ), http_build_query($params)));

    $key = rawurlencode($consumer_secret).'&'.rawurlencode($token_secret);

    return base64_encode(hash_hmac('sha1', $base, $key, true));
}

リクエストパラメータをアルファベット順に並べる
ksort($params); って本当に必要あるのか甚だ疑問である。

リクエストトークン発行+リダイレクト

さて本題の、リクエストトークン発行からリダイレクトさせるまでのプログラム。
まずは必要になるものを定数に入れておきます。

// Twitterアプリのコンシューマキー
define('CONSUMER_KEY', 'I***********************D');
define('CONSUMER_SECRET', 'S************************************************H');

// アプリケーション設定
define('CALLBACK_URL', 'http://hogehoge.mekachan.net/callback.php');

// TwitterAPIのエンドポイント
define('RTOKEN_URL', 'https://api.twitter.com/oauth/request_token');
define('AUTH_URL', 'https://api.twitter.com/oauth/authenticate');
define('UPDATE_URL', 'https://api.twitter.com/1.1/statuses/update.json');
リクエストトークンの発行

どんな実装でも、下記でリクエスト飛ばせばリクエストトークンを取得できます。

$params = array(
    'oauth_callback'            => CALLBACK_URL,
    'oauth_consumer_key'        => CONSUMER_KEY,
    'oauth_nonce'               => md5(microtime() . mt_rand()),
    'oauth_timestamp'           => time(),
    'oauth_version'             => '1.0',
    'oauth_signature_method'    => 'HMAC-SHA1',
);
$params['oauth_signature'] = $this->build_signature('GET', RTOKEN_URL, $params, CONSUMER_SECRET);
$res = file_get_contents(RTOKEN_URL . '?' . http_build_query($params));
parse_str($res, $token);

$res にリクエストトークンが入ってきます。
クエリ文字列形式でレスポンスが返ってくるため、parse_str() で分解して扱います。

認証ページにリダイレクト
if(false===isset($token['oauth_token'])){
    // エラー発生
    $this->throwException(400, array('redirect_url' => AUTH_URL.'?'.http_build_query($token)));
}

// 認証ページにリダイレクト
$this->throwException(302, array('redirect_url' => AUTH_URL.'?'.http_build_query(array('oauth_token'=>$token['oauth_token']))));

$token['oauth_token'] に値がセットされていない場合、
リクエストトークンの取得に失敗しています。エラーを出力しましょう。

認証ページへのリダイレクトをページ遷移で実装する場合は下記ですね。
header('Location: '.AUTH_URL.'?'.http_build_query(array('oauth_token'=>$token['oauth_token'])));

アクセストークンを取得

さて、Twitterの認証ページから戻ってきて、先ほどCALLBACK_URLに登録した
http://hogehoge.mekachan.net/callback.php に遷移しました。
ここではアクセストークンを取得します。

アプリ承認が拒否された場合

アプリの承認がされず、アプリケーションに戻ってきた場合、
http://hogehoge.mekachan.net/callback.php?denied=~~~ というアクセスになります。
isset($_GET['denied']) で、「認証エラーが発生しました」、などを表示するようにしましょう。

アプリ承認が通ったらアクセストークンを取得

まずは定数を設定します。コンシューマキーに加えて、アクセストークン取得のエンドポイントを入れます。

// Twitterアプリのコンシューマキー
define('CONSUMER_KEY', 'I***********************D');
define('CONSUMER_SECRET', 'S************************************************H');

// アクセストークン取得のエンドポイント
define('ACCESS_URL', 'https://api.twitter.com/oauth/access_token');

で、ここでも先ほど作った build_signature() を使います。

    $params = array(
        'oauth_consumer_key'        => CONSUMER_KEY,
        'oauth_nonce'               => md5(microtime() . mt_rand()),
        'oauth_signature_method'    => 'HMAC-SHA1',
        'oauth_timestamp'           => time(),
        'oauth_token'               => $_GET['oauth_token'],
        'oauth_verifier'            => $_GET['oauth_verifier'],
        'oauth_version'             => '1.0',
    );

    $params['oauth_signature'] = build_signature('POST', ACCESS_URL, $params, CONSUMER_SECRET, empty($_SESSION['access_token_secret']) ? '' : $_SESSION['access_token_secret']);

パラメータをまとめて、シグネチャ生成。
シグネチャもパラメータに入れ直して、以下でPOST送信します。

    $header_params = http_build_query($params, '', ',');

    // リクエスト用のコンテキスト
    $content = http_build_query(array());
    $context = array(
        'http' => array(
            'method' => 'POST', // リクエストメソッド
            'header' => join("rn", array(           // ヘッダー
                'Authorization: OAuth ' . $header_params,
                'Content-Type: application/x-www-form-urlencoded',
                'Content-Length: ' . strlen($content)
            )),
            'content' => $content,
        ),
    );

    try{
        $res = file_get_contents(ACCESS_URL, false, stream_context_create( $context ));
        parse_str($res, $access_token);
    }
    catch(Exception $e){ // エラー発生
        echo $e->getMessage();
        return;
    }

    // アクセストークンとシークレットの取得
    $_SESSION['access_token'] = $access_token['oauth_token'];
    $_SESSION['access_token_secret'] = $access_token['oauth_token_secret'];

アクセストークンの取得に成功すると、
レスポンスのクエリ文字列形式で oauth_token と oauth_token_secret が取得できます。
SESSIONに入れておきましょう。

アクセストークンを使ってAPIを呼ぶ

アクセストークンが取得できたら、それを利用してツイート投稿のAPIが実行できます。
これもエンドポイントが変わるだけで今までとほとんど同じ流れなのでサクサクっと説明を進めます。

$_POST['description'] がツイート文言に当たる文言です。
パラメータをまとめてシグネチャ生成、シグネチャをパラメータに入れ直し。

$params = array(
    'status'                    => urldecode($_POST['description']),
    'oauth_token'               => $access_token,
    'oauth_consumer_key'        => CONSUMER_KEY,
    'oauth_signature_method'    => 'HMAC-SHA1',
    'oauth_timestamp'           => time(),
    'oauth_nonce'               => microtime(),
    'oauth_version'             => '1.0',
);

$params['oauth_signature'] = $this->build_signature('POST', UPDATE_URL, $params, CONSUMER_SECRET, $access_token_secret);
$header_params = http_build_query( $params, '', ',' );

// リクエスト用のコンテキスト
$content = http_build_query(array('status'=>$params['status']));
$context = array(
    'http' => array(
        'method' => 'POST',  // リクエストメソッド
        'header' => join("rn", array(              // ヘッダー
            'Authorization: OAuth ' . $header_params,
            "Content-Type: application/x-www-form-urlencoded",
            "Content-Length: " . strlen($content)
        )),
        'content' => $content,
    ),
);

try{
    $json = file_get_contents(UPDATE_URL, false, stream_context_create( $context ));
}
catch(Exception $e){
    echo $e->getMessage();
}
// JSONをオブジェクトに変換
$obj = json_decode($json);

file_get_contents()で、ツイート投稿APIのエンドポイントにPOST送信。

if(isset($obj->errors) && $obj->errors != ''){
    // エラー処理
}

API実行のレスポンス結果は、JSON形式で返ってきます。(エンドポイント名も.jsonだし)
文字数オーバーやアクセストークンの期限切れなどのエラーが、
$obj->errors に入っているので、エラー処理はご自由に。

結果

ライブラリを使わず、TwitterのOAuth認証・ツイート投稿ができました。
ちなみにCURLも使っていないので、CURLモジュールが入ってなくても大丈夫。
環境に依存せず、ファイルを置くだけでTwitterでブヒブヒできます。

元記事はこちら

TwitterのOAuth認証その3:実装編