ツイキャス API V2を使って認証ページを作った

ツイキャスAPIがAPIV2になり、認証が必要になりました。
そろそろ対応を検討しようと思い、認証部分を実装してみました。
開発API - ツイキャス
http://twitcasting.tv/indexapi.php

1. CreateApp
============
ツイキャスにログインし、次のURLにアクセスします。
My Apps
https://ssl.twitcasting.tv/developer.php
![20180503_TwcasChatListen_CreateApp 0.jpg](https://qiita-image-store.s3.amazonaws.com/0/252253/d45f752b-30b6-d537-d91d-285d9fc980b2.jpeg)

[Create New App]ボタンをクリックし、アプリの情報を設定します。
ここで、コールバックURLが必須になっており、WindowsアプリやWebアプリを作る場合はWebサーバーを立てる必要があります。

![20180503_TwcasChatListen_CreateApp 2.png](https://qiita-image-store.s3.amazonaws.com/0/252253/00ee77f1-efa7-4b3f-d9fe-54970f1138a6.png)

[Create]ボタンをクリックすれば登録完了です。
![20180503_TwcasChatListen_CreateApp 1.jpg](https://qiita-image-store.s3.amazonaws.com/0/252253/08d35e90-0e84-d299-1f18-b5ebf4029490.jpeg)

2.  APIの呼び出し
================
次のライブラリを使用しました。
TwitCastingOAuth
https://github.com/shucream0117/twitcasting-oauth

**2.1. 認証URLへリダイレクト**
まず、confirmページにアクセスさせます。
このページでは認証URLを取得し、リダイレクトしています。
認証URLの取得にはAuthCodeGrant->getConfirmPageUrl()を使います。

    /**
     *  確認ページリダイレクト
     */
    public function confirm() {
        // CSRFトークン
        $csrfToken = md5(uniqid(rand(),1));

        // AuthGrandの生成
        $grant = new AuthCodeGrant(ClientId, ClientSecret, CallbackUrl);

        // 認証ページURLの取得
        $url = $grant->getConfirmPageUrl($csrfToken);

        // セッションデータのセット
        $this->session->set(CsrfTokenKey, $csrfToken);

        // リダイレクト
        $this->response->redirect($url);
    }

**2.2. 連携アプリを許可**
リダイレクト先では[連携アプリの許可]か[キャンセル]が選べるようになっています。

![20180503_TwcasChatListen 連携アプリを許可.jpg](https://qiita-image-store.s3.amazonaws.com/0/252253/0f2c0ffa-19a4-11b2-4486-f7e2a403d1b0.jpeg)

**2.3. アクセストークンの取得**
連携アプリの許可ページでアクションを起こすとコールバックURLにリダイレクトされます。
連携アプリが許可された場合は、コールバックURLにcodeパラメータが付与されます。
このcodeパラメータを使えばアクセストークンを取得できます。
アクセストークンの取得にはAuthCodeGrant->requestAccessToken()を使います。
なお、キャンセルされた場合はresultパラメータがdeniedになります。

    /*
     * コールバック
     */
    public function callback() {
        $req = $this->request;
        $req_base = $req->getBasePath();
        $this->view->req_base = $req_base;
        $req_base_url = $req->getBaseUrl();
        $this->view->req_base_url = $req_base_url;
        
        // GETパラメータ
        $query = $req->getQuery();
        if (isset($query['result'])) {
            $result = $query['result'];
            if ($result == 'denied') {
                // 認証がキャンセルされた
                $this->_authFailed();
                return;
            }
        }
        
        // CSRFトークンのチェックをここで実装
        //  あとで

        $code = $query['code'];

        // AuthGrandの生成
        $grant = new AuthCodeGrant(ClientId, ClientSecret, CallbackUrl);

        // アクセストークンのリクエスト
        $accessToken = $grant->requestAccessToken($code, new AppExecutor(ClientId, ClientSecret));
        
        $accessTokenStr = $accessToken->getAccessToken();
        
        $pageData = array(
            'confirmed' => 1,
            'accessToken' => $accessTokenStr,
        );
        
        $this->view->pageData = $pageData;
    }

![20180502_usamini_TwcasChatListen認証完了.jpg](https://qiita-image-store.s3.amazonaws.com/0/252253/dacf4520-96a6-4168-324d-057a8a1b789b.jpeg)

3. 全ソースリスト
===============

```twcaschatlisten_controller.php
<?php

require_once 'vendor/autoload.php'; // TwitCastingOAuth
use Shucream0117\TwitCastingOAuth\GrantFlow\AuthCodeGrant;
use Shucream0117\TwitCastingOAuth\ApiExecutor\AppExecutor;

/**
 * 定数
 */
const ClientId = 'ClientId';
const ClientSecret = 'ClientSecret';
const OwnerId = 'OwnserId';
const CallbackUrl = 'http://example.com/twcaschatlisten/callback';
const CsrfTokenKey = 'csrfToken';
/**
 * TwcasChatListenコントローラー
 */
class TwcasChatListenController extends Controller {

    /**
     * プリプロセス
     */
    public function preProcess() {
        $this->view->setLayout('default_twcaschatlisten');
    }

    /**
     *  確認ページリダイレクト
     */
    public function confirm() {
        // CSRFトークン
        $csrfToken = uniqid();

        // AuthGrandの生成
        $grant = new AuthCodeGrant(ClientId, ClientSecret, CallbackUrl);

        // 認証ページURLの取得
        $url = $grant->getConfirmPageUrl($csrfToken);

        // セッションデータのセット
        $this->session->set(CsrfTokenKey, $csrfToken);

        // リダイレクト
        $this->response->redirect($url);
    }

    /*
     * コールバック
     */
    public function callback() {
        $req = $this->request;
        $req_base = $req->getBasePath();
        $this->view->req_base = $req_base;
        $req_base_url = $req->getBaseUrl();
        $this->view->req_base_url = $req_base_url;
        
        // GETパラメータ
        $query = $req->getQuery();
        if (isset($query['result'])) {
            $result = $query['result'];
            if ($result == 'denied') {
                // 認証がキャンセルされた
                $this->_authFailed();
                return;
            }
        }
        
        // セッションデータの取得
        $csrfTokenSaved = $this->session->get(CsrfTokenKey);
        if (!$csrfTokenSaved) {
            //echo 'session has been expired<br>';
             $this->_authFailed();
             return;
        }
        
        if (!isset($query['state'])) {
            //echo 'csrf token is invalid<br>';
            $this->_authFailed();
            return;
        }
        $state = $query['state'];
        if ($state != $csrfTokenSaved) {
            //echo 'csrf token is invalid<br>';
            $this->_authFailed();
            return;
        }
        $code = $query['code'];

        // AuthGrandの生成
        $grant = new AuthCodeGrant(ClientId, ClientSecret, CallbackUrl);

        // アクセストークンのリクエスト
        $accessToken = $grant->requestAccessToken($code, new AppExecutor(ClientId, ClientSecret));
        
        $accessTokenStr = $accessToken->getAccessToken();
        
        $pageData = array(
            'confirmed' => 1,
            'accessToken' => htmlspecialchars($accessTokenStr),
        );
        
        $this->view->pageData = $pageData;
    }
    
    /**
     * 認証失敗
     */
    private function _authFailed() {
        $pageData = array(
            'confirmed' => 0,
            'accessToken' => '',
        );
        $this->view->pageData = $pageData;
    }
}
```