2014年3月6日木曜日

PHPで「Lusitanian / PHPoAuthLib」を使って「Sign in with Twitter」なコードを書いてみる

ボクもWebサービスとか作ってみたい!(><)


ちょっと自分の知識の幅を広げるため、軽い気持ちでWebサービスを立ち上げてみようかなと思っているわけで。

まあ、Webサービスといっても自分のお勉強用なのでたいしたことは無いのですが、一応ちょっとした構想だったり、使いそうな/使いたい技術的な下調べが終わっている段階です。

ので、これからそういう感じのネタをいろいろ備忘録的に書くこともあるかもしれません。これがその第一弾なので。

IDとパスワードの情報とか手元に持っておきたくない!


さて、Webサービスと一口に言ってもいくつか種類があるわけで。私が作ろうとしているのは会員制……というかログインが前提のサービスで、SNSとの連動がキモだったりするのです。

Web1.0(*1)時代のWebサービスと言えば、サービス毎にアカウントを作るのが当たり前でしたね。使うサービスの数だけIDとパスワードが増えていく。直感的に分かりやすくはあるものの、アカウント情報の管理が大変なことになるわけで。

サービスを作る側から見ても、ログインIDとパスワードという重要な情報を保持する必要があるということは結構気を遣うところですよね。漏れないようにしなきゃいけないし。

私がいざ技術的お勉強用のミニマムなWebサービスを立ち上げようと思ったとき、前時代的なアカウントの概念を持ち込むとなると、実装的には楽(*2)なものの運用的なことを考えるとちょっと躊躇してしまうわけです。

そんなときに便利なのがOAuth。要はみんなが普通にアカウント持ってるTwitterやらFacebookやらで自分のWebサービスにログインできるようにしてしまいましょう!……というのがこのエントリの狙い。

とりあえず、このエントリではTwitterでログインできるような仕組み作りにチャレンジしてみるテストです。

まずは下準備


Twitter自体のアカウントは持っていることは前提。それ以降からのお話がここに書いた内容。OAuth自体の仕組みとかは書きません。

前置きはこのくらいにして……。

まず、Twitter Developersにアプリの登録をしなければ。


ってことで、まずは普段おつかいのTwitterアカウントでログイン。このアカウントでアプリに関する情報を管理するので、場合によっては普段遣ってるのとは別アカウントにしておいたほうが幸せになれるのかも。


で、右上にログインしたTwitterアカウントのアイコンが出ているのでマウスを乗っける。するとメニューが出てくるので「My applications」をクリック。


「Create New App」をクリック。


「Application details」に必要事項を入力。「Name」がアプリの名称。「Description」がアプリの簡単な説明。「Website」がアプリのウェブサイトのURL。「Callback URL」が認証成功後に飛んできて欲しいURL。アプリを作る際はたぶん「Callback URL」が一番大事なのかな?。


下の方に重要な「Rule」が書いてあるので良く読む。同意できるようなら「Yes, I agree」にチェックを付けて、「Create your Twitter application」をクリック。


特に問題なければこんな感じで登録される。この「Detail」タブには登録したアプリケーションの概要が表示されている模様。下の方にはなにげに重要そうな情報がリストされている。あと、アプリを消したりもここからできるっぽいね。



「Settings」タブにはアプリ登録時に入力した内容を修正したり、アイコンの登録などアプリ作成時には指定できない情報なんかを追加/変更できるっぽい。


ここの「Application details」にある「Allow this application to be used to Sign in with Twitter」のチェックボックスにチェックを付けておかないと、自分のアプリからTwitterを使ったログインができないらしいので忘れないで付けておく。


後はアイコンの指定とか、アプリ開発した会社なんかの情報を入れられるフォームが続く。必要ならここで指定すればいい。


「API Keys」タブにはAPIキーなど外部に漏れたらマズイ情報が集まってる。アプリを作る上では必要になるので必要に応じて参照しよう。特に「API Key」と「API secret」は絶対必要。



最後に「Permission」タブ。アプリのアクセス権を制御する画面らしい。作るアプリに応じて変更するんだけど、ボクの場合はアプリからTweetもさせたいから、ここを「Read and Write」にしておくとよさげ。


さて、だいたいこのくらいかな。必要な情報などは必要になったタイミングでこれらを参照すればいいので、いつでもアクセスできるようにしておこう。一度Webブラウザを閉じてしまっても、またTwitter Developersにログインして右上のアイコンにオンマウスしたときに表示されるメニューより「My applications」をクリックするとこんな感じで出てくるから大丈夫。


コーディングするよ!


ってことで後はひたすらコーディングするだけ。

コーディングの前提なんだけども、ボクはRisoluto2.x系を使ってそいつがデフォルトでセットアップしてくれる「Lusitanian / PHPoAuthLib」ってライブラリを使うことにしている。このエントリに掲載したコードは、基本的にこのライブラリに同梱されているサンプルコードをベースにボクが使いやすい形に修正したものなので、Risoluto使ってない環境でも参考にできるはず。


参考になるといえば、今から実装しようとしている「Sign in with Twitter」のフローについては下記のドキュメントが参考になる。基本的にライブラリが小難しいところを吸収してくれるから、意識することはすくないけど。覚えておくとトラブルシュートに役立つかも。


Top画面を作ろう!


まずはTop画面をば。

単に「Sign in with Twitter」のボタンなりリンクなりを表示させるだけの画面なんだけど、メンドクサイので自動的に認証画面に飛んでくれるようなコードにしてみた。

例えば、こんな感じ。一応Risoluto固有のコードはできるだけ排除してあるので、5〜7行目の「Use」と「Top::Play()」の中身だけに目を向けて貰えれば他の環境でもOK(*3)。

<?php
namespace RisolutoApps;

// 必要なライブラリを読み込む
use OAuth\OAuth1\Service\Twitter;
use OAuth\Common\Storage\Session;
use OAuth\Common\Consumer\Credentials;

class Top extends \Risoluto\RisolutoControllerBase
{
    public function Play()
    {
        // OAuthに必要なブツをセット
        $storage = new Session();
        $credentials = new Credentials(
                                          '(API(Consumer) key)',
                                          '(API(Consumer) secret)',
                                          '(Callback URL)'

        // Twitterのサービスを使うためのクラスインスタンスを生成
        $serviceFactory = new \OAuth\ServiceFactory();
        $twitterService = $serviceFactory->createService('twitter', $credentials, $storage);

        // APIアクセス用のトークンをゲットして、Twitterの認証画面に飛ぶ
        $token = $twitterService->requestRequestToken();
        $url = $twitterService->getAuthorizationUri(array('oauth_token' => $token->getRequestToken()));

        header('Location: ' . $url);

        return true;
    }
}

5〜7行目の「Use」で始まる部分は、今回使用した「Lusitanian / PHPoAuthLib」が必要としているもの。一応、全部必須っぽい。

14〜18行目はAPIを叩くために必要なものいろいろ。

14行目はライブラリが必要としているもので、取得した情報なんかを格納しておくクラスのインスタンス。ライブラリ的にはセッションに必要な情報を格納するのだけれど、それを実現するためのクラス。

15〜18行目はAPIを叩くために必要な情報。API key/secret(*4)は隠匿すべき情報(って認識でいいよね?)。Callback URLはTwitter Developersの「Settings」タブで入力したものと同じのをとりあえず設定してみた。

21〜22行目でTwitterのAPIを叩くために必要なライブラリのインスタンスをつくってる。25〜26行目で認証用のURLをゲット。

で、最後に28行目でその認証用URLに飛ばして終了。こちらはライブラリの使い方以外は特に難しい事は無いはず。

認証後に飛んでくる画面を作ろう!


ってことで、メインのロジックを書いてみようか。

こちらは「Twitter Developers」のサイトで「Setting」タブにて指定した「Callback URL」ってのがあったと思うんだけど、そこに指定したURLにアクセスしたら呼び出されるコードがこちら。……変な説明だな(;´Д`)

こちらも一応Risoluto固有のコードはできるだけ排除してあるので、5〜7行目の「Use」と「Auth::Play()」の中身だけに目を向けて貰えれば他の環境でもOK(*3)。

<?php
namespace RisolutoApps;

// 必要なライブラリを読み込む
use OAuth\OAuth1\Service\Twitter;
use OAuth\Common\Storage\Session;
use OAuth\Common\Consumer\Credentials;

class Auth extends \Risoluto\RisolutoControllerBase
{
    public function Play()
    {
        // OAuthに必要なブツをセット
        $storage = new Session();
        $credentials = new Credentials(
                                          '(API(Consumer) key)',
                                          '(API(Consumer) secret)',
                                          '(Callback URL)'

        // Twitterのサービスを使うためのクラスインスタンスを生成
        $serviceFactory = new \OAuth\ServiceFactory();
        $twitterService = $serviceFactory->createService('twitter', $credentials, $storage);



        // とりあえずGET/POSTで取得した情報を表示してみる
        echo "<h1>取得した情報を表示</h1>";
        echo "<h2>GET</h2>";
        echo "<pre>" . print_r($_GET, true) . "</pre>";
        echo "<h2>POST</h2>";
        echo "<pre>" . print_r($_POST, true) . "</pre>";
        echo "<h2>SESSION</h2>";
        echo "<pre>" . print_r($_SESSION, true) . "</pre>";

        // これ以降はログインに成功した場合のみ
        if (!empty($_GET['oauth_token']) and !empty($_GET['oauth_verifier'])) {
            // APIアクセス用のトークンをセッションから取得する
            $token = $storage->retrieveAccessToken('Twitter');

            // Twitterのアカウント情報をゲットするぜ!
            $twitterService->requestAccessToken(
                $_GET['oauth_token'],
                $_GET['oauth_verifier'],
                $token->getRequestTokenSecret()
            );

            // 取得した情報はJSON形式なので、デコードしてから表示する
            $result = json_decode($twitterService->request('account/verify_credentials.json'));

            echo "<h2>認証したアカウントのユーザ情報</h2>";
            echo "<pre>" . print_r($result, true) . "</pre>";
        }
        return true;
    }
}

5〜7行目の「Use」で始まる部分は、今回使用した「Lusitanian / PHPoAuthLib」が必要としているもの。14〜18行目はAPIを叩くために必要なものいろいろ。21〜22行目でTwitterのAPIを叩くために必要なライブラリのインスタンスをつくってる。

……っと、ライブラリ的な話をすると、ここまでは共通で必要なものっぽい。外出しにしておくが吉。

27〜33行目は、単にこのコードが呼ばれたときにGET/POSTでどんな値が渡ってくるのか、そしてセッションにはどんな情報が入っているのかが見たかったので書いてあるだけ。

36〜52行目は認証に成功したときだけ実行されるコード。38〜45行目でAPI叩くのに必要な情報をゲットして、48行目でAPIを叩いてる。ここでは認証したユーザの情報を取得するAPIを叩いてる。取得した情報はJSON形式で返ってくるようにしているので、それをデコードして表示……という流れ。


こちらもライブラリの使い方自体は難しくないはず。ライブラリの使い方も、基本は小難しいことを全部吸収してくれているはずなので、この程度だったら難しい事はないはず。

実際の認証に使う場合は、必要な情報を取得してDBなりセッションなりに保持させてゴニョゴニョすればいい……って感じかな?

動きを見てみよう!


ってことで、このコードを実際に動かして見てみることにしよう。

初回アクセス時(未ログイン、認証キャンセル)


未ログイン状態でTopページにアクセスした場合でキャンセルした場合。

下記のようにログインページが表示されるが、ここでキャンセルを押してみる。


するとこんな画面になる。「○○に戻る」のボタンを押すと……。


認証後に飛んでくるって方の画面(Callbackの方)に飛んでくる。注目すべきはGETで渡ってくる「denied」の値。これが渡ってきたら認証できてないって事が分かるらしい。


初回アクセス時(未ログイン、認証成功)


未ログイン状態でTopページにアクセスした場合で認証に成功した場合。

Topにアクセスしたらログインフォームが表示されるのは同じ。ここでちゃんとしたIDとパスワードでログインしてみる。

すると認証後に飛んでくる……って方の画面(Callback URLね)に飛んでくるんだけど、失敗したときとGETで取得できる情報が異なる。あと、その後のAPIアクセスによって、認証したユーザの情報が取得できている。




Twitterの「アプリ連携」の項目にしっかり追加されているのが確認できる。


2回目以降(ログイン済み)


1度ログインしてしまえば、2回目以降Topにアクセスするとそのまま認証後に飛んでくる……って方の画面(Callback URLね)に飛んでくる。取得できる情報は前述の初回ログイン成功時と同様なので、認証処理をするならそこら辺をうまく使えばよさげかな。

とりあえず「Sign in with Twitter」はいけそうな気がする!


ここまでの知識で、何となくだけど「Sign in with Twitter」は実現できそうに思える。ここから先、自分のWebサービスでどう使うのかとか、WebサービスからTweetさせるにはどうすればいいのか(*5)とか、そういう部分はもうちょっと検討したり調べなきゃいけないだろうけど。

特にWebサービスからTweetさせるコードについては比較的汎用性のある処理だと思われるので、また自分用のメモがてらここにまとめてみるつもり。

このコードもなんか勘違いしてたりする部分があると思うので、「ここはこうしちゃダメ!」とか「ここはこうした方がいいよ!」とか「ウザイ」とかアドバイスしてくれる人がいるかもしれないという淡い期待を抱いてみよう。


*1:えっと、Web2.0までは聞いたことあるんですが、今はいくつになってるのかな?
*2:質はともかく、さんざん今まで作ってきたので
*3:RisolutoではComposerのオートローダを使ってるので、それと同等なものが使える環境じゃないとこのコードは動かない
*4:ちなみにConsumer key/secretと同じものだよ
*5:ユーザ情報を取得する代わりにツイートするAPIを叩けばいけるのは分かるけど、そうじゃなくてこの流れとは別にそういったのを実行する方法のことね

0 件のコメント:

コメントを投稿