概要
PHPもCakePHPもTwitter OAuth認証も初めてな人間がTwitter OAuth認証を実装した話
開発環境
CakePHP 3.5.13
PHP 5.6.30
Twitter認証にはAbraham\TwitterOAuthを使う。
参考:【CakePHP3】Twitter API ライブラリTwitterOAuthをComposerでインストール – atomicbox
準備
APIキーの取得は済ませておく。
Twitter Application Management
取得方法については調べればいくらでも出てくる。
Twitter OAuth認証の流れ
図付きの説明が公式にある。(英語)
Implementing Sign in with Twitter — Twitter Developers
https://twitteroauth.com/ にライブラリを使ったPHPの動作デモがあるので分かりやすい。
- Twitterにrequest_token, request_token_secretを発行してもらう
POST oauth/request_token — Twitter Developers
4で検証に使うので、セッションに保存しておく。 - Twitterに認証URLを発行してもらう
GET oauth/authorize — Twitter Developers
GET oauth/authenticate — Twitter Developers
authorizeのほうだと毎回認証確認され、authenticateのほうだと認証済みの場合はすぐにコールバックされる。 - 2で発行したURLにリダイレクトする
- 認証完了後、コールバックで指定したURLに返ってくるのでよしなにする
- 1で発行したトークンとコールバックで帰ってきたトークンが同一かどうかチェックする
- 認証拒否時のリダイレクト処理など
-
request_token, request_token_secret, oauth_verifierを用い、Twitterにaccess_token, access_token_secretを発行してもらう
POST oauth/access_token — Twitter Developers
これが投稿などを行う時に必要なキーとなる。セッションに保存しておく
この段階でOAuth認証が完了した扱いになる。(=ユーザーの連携アプリ一覧に表示される) - 以降、5で取得したトークンを用いてTwitter APIを叩く
Component実装
やることは分かったのでコンポーネントとして実装する。
- コントローラー側ではTwitter API、セッション管理を意識しないように
- getAuthenticateUrlを呼びリダイレクト
- コールバック側でvalidateCallbackを呼び出す
- その後postTweetを呼び出す
- tokenなどはセッションに保存
- Twitter.oauth_token
- Twitter.access_token
- Twitter.user_id
- 異常系はとりあえずInternalErrorExceptionをthrowする
- とりあえずtokenは削除する
- とりあえずツイートの投稿のみ実装
<?php
namespace App\Controller\Component;
use Cake\Controller\Component;
use Abraham\TwitterOAuth\TwitterOAuth;
use Cake\Network\Exception\InternalErrorException;
class TwitterComponent extends Component {
// アプリケーションのConsumer Key (API Key)
const TWITTER_CK = '<Consumer Key (API Key)>';
// アプリケーションのConsumer Secret (API Secret)
const TWITTER_CS = '<Consumer Secret (API Secret)>';
// アプリケーションのCallback URL
const CALLBACK_URL = '<Callback URL>';
public function initialize(array $config) {
$this->controller = $this->_registry->getController();
$this->session = $this->controller->request->session();
}
/**
* 現在のセッションでTwitter認証が済んでいるかを判定する
*
* @return bool
*/
public function isAuthorized()
{
return $this->getAccessToken() !== null;
}
/**
* ユーザー認証URLを取得する
*
* @return string
*/
public function getAuthenticateUrl()
{
$connection = new TwitterOAuth(self::TWITTER_CK, self::TWITTER_CS);
$request_token = $connection->oauth('oauth/request_token', array('oauth_callback' => self::CALLBACK_URL));
$authenticate_url = $connection->url('oauth/authenticate', array('oauth_token' => $request_token['oauth_token']));
if (!isset($request_token) || !isset($authenticate_url))
{
$this->clearSessionData();
throw new InternalErrorException('Twitter認証に失敗しました');
}
$this->setRequestToken($request_token["oauth_token"], $request_token["oauth_token_secret"]);
return $authenticate_url;
}
/**
* コールバックで初期化を行う
*/
public function initializeOnCallback()
{
$query = $this->controller->request->query;
if(isset($query["denied"]))
{
$this->clearSessionData();
throw new InternalErrorException('認証がキャンセルされました');
}
// Twitterから返却されたOAuthトークンとセッションに保存されたOAuthトークンを比較
$return_oauth_token = (isset($query['oauth_token'])) ? $query['oauth_token'] : null;
$request_token = $this->getRequestToken();
if (!isset($request_token) || $return_oauth_token != $request_token['token'])
{
// セッション削除
$this->clearSessionData();
throw new InternalErrorException('OAuthトークンが無効です');
}
$this->createAccessToken($query['oauth_verifier']);
}
/**
* ツイートする
*
* @param string $message ツイート内容
*/
public function postTweet($message)
{
$connection = $this->createConnection();
$statues = $connection->post("statuses/update", ["status" => $message]);
if ($connection->getLastHttpCode() != 200)
{
$this->clearSessionData();
throw new InternalErrorException('ツイートに失敗しました');
}
}
/**
* アクセストークンの取得を行う
*/
private function createAccessToken($oauth_verifier)
{
$request_token = $this->getRequestToken();
if (!isset($request_token) || !isset($oauth_verifier))
{
$this->clearSessionData();
throw new InternalErrorException('OAuth認証情報が存在しません');
}
$connection = new TwitterOAuth(self::TWITTER_CK, self::TWITTER_CS, $request_token["token"], $request_token["token_secret"]);
$access_token = $connection->oauth("oauth/access_token", ["oauth_verifier" => $oauth_verifier]);
if ($connection->getLastHttpCode() != 200)
{
$this->clearSessionData();
throw new InternalErrorException('アクセストークンの取得に失敗しました');
}
$this->setAccessToken($access_token["oauth_token"], $access_token["oauth_token_secret"]);
$this->setUserId($access_token["user_id"]);
return $this->getAccessToken();
}
/**
* TwitterAPIに接続するためのconnectionを取得する
*
* return TwitterOAuth
*/
private function createConnection()
{
$access_token = $this->getAccessToken();
return new TwitterOAuth(self::TWITTER_CK, self::TWITTER_CS, $access_token["token"], $access_token["token_secret"]);
}
/**
* セッションにOAuth認証のトークンを保存する
*/
private function setRequestToken($oauth_token, $oauth_token_secret)
{
$this->session->write('Twitter.oauth_token', array("token" => $oauth_token, "token_secret" => $oauth_token_secret));
}
/**
* セッション情報からOAuth認証のトークンを取得する
*
* @return null | array["token", "token_secret", ]
*/
private function getRequestToken()
{
return $this->session->read('Twitter.oauth_token');
}
/**
* セッションにTwitterのアクセストークンを保存する
*/
private function setAccessToken($oauth_token, $oauth_token_secret)
{
$this->session->write('Twitter.access_token', array("token" => $oauth_token, "token_secret" => $oauth_token_secret));
}
/**
* セッション情報からTwitterのアクセストークンを取得する
*
* @return null | array["token", "token_secret"]
*/
private function getAccessToken()
{
return $this->session->read('Twitter.access_token');
}
/**
* セッションにTwitterのuser_idを保存する
*/
private function setUserId($user_id)
{
$this->session->write('Twitter.user_id', $user_id);
}
/**
* セッション情報からTwitterのuser_idを取得する
*
* @return string
*/
public function getUserId()
{
return $this->session->read('Twitter.user_id');
}
/**
* セッションに保存されているOAuth認証情報などをクリアする
*/
private function clearSessionData()
{
$this->session->delete('Twitter.oauth_token');
$this->session->delete('Twitter.access_token');
$this->session->delete('Twitter.user_id');
}
}
アクセストークンの有効期限について
Oauth FAQ — Twitter Developers
How long does an access token last?
Access tokens are not explicitly expired. An access token will be invalidated if a user explicitly revokes an application in the their Twitter account settings, or if Twitter suspends an application. If an application is suspended, there will be a note on the apps.twitter.com page stating that it has been suspended.What if an access token becomes invalid?
Assume a user’s access token may become invalid at any time. If this happens, prompt the user to re-authorize the application. Ensuring that this situation is handled gracefully is important for a good user experience.
一度取得したアクセストークンは特に有効期限などは設定されておらず、ユーザーが連携を解除するまでは使える模様。
しかし、ユーザーが連携を解除することでアクセストークンが無効になっている可能性が常に存在するため、エラーハンドリングはよしなにしてくれとの事。
Response Codes — Twitter Developers
認証失敗の場合はHTTP Status Code == 401(Unauthorized)が返ってくるはずなので、こちら側でいい感じに制御する。
まとめ
PHP触ったこと無いけど一通り実装してみた。思ったより簡単だった。普段Railsを触っているのでCakePHPはすんなり理解できた。
PHPのお作法的な所はよく分からないので、微妙な実装をしている所があるかもしれない。
ざざっと書いたので色々と雑。
参考
https://qiita.com/michimani/items/a74f8debeace2df63eec
https://qiita.com/frost_star/items/f652a798a211d606d60d