OAuth 2.0 認証で YouTube Data API v3 を利用する

OAuth 2.0認証を行ってYouTube Data API v3を利用するための手順は、

  • OAuth同意画面の設定
  • OAuthクライアントIDの作成
  • Googleの承認サーバーURLにアクセスして、アプリケーションにAPIの実行を許可&承認コードを取得
  • 承認コードを使ってアクセストークンを取得
  • 期限が切れた場合、更新トークンを使って新しいアクセストークンを取得

となる。

まず、Google Cloud Platform上でOAuth同意画面を設定する。

Google Cloud Platformの "APIとサービス" → "OAuth同意画面" ページに移動して下記の設定を行う。
※この設定は、プレイリストのインポート機能の開発テスト用にAPIを利用することを前提にしている

  1. ユーザーの種類を選択する。
    G Suiteを利用していれば "内部" の選択が可能。利用していなければ "外部" を選択することになる。
    ※G Suiteのアカウントから作成されたものであってもYouTubeブランドアカウントはG Suiteの組織内ユーザーとみなされない。よって、YouTubeブランドアカウントのデータをAPIから操作したい場合は "外部" を選択する必要がある。
  2. アプリケーション名を入力する
  3. スコープの追加
    ./auth/youtube を追加する。(APIでプレイリストの作成をリクエストするために必要)
  4. 保存

次に、Google Cloud Platform上でOAuthクライアントIDを作成する。

Google Cloud Platformの "APIとサービス" → "OAuth同意画面" ページに移動する。

  1. "+認証情報を作成" をクリック
  2. "OAuth クライアント ID" を選択
  3. アプリケーションの種類 "デスクトップアプリ" を選択
  4. 作成

以上で、Google Cloud Platform上で行う設定は完了。


続いて、承認コードを取得する。承認コードを取得するには、Googleの承認サーバーURLにアクセスする必要がある。

下記は、Googleの承認サーバーURLを表示するためのコード。


require_once '/path/to/google-api-php-client/vendor/autoload.php';

$CLIENT_ID = '_CLIENT_ID_';
$CLIENT_SECRET = '_CLIENT_SECRET_';
$REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob';

$client = new Google_Client();
$client->setClientId($CLIENT_ID);
$client->setClientSecret($CLIENT_SECRET);
$client->setRedirectUri($REDIRECT_URI);
$client->setAccessType('offline');
$client->setScopes('https://www.googleapis.com/auth/youtube');

echo $client->createAuthUrl() . "\n";

上記コードで行っているのは、クライアントライブラリを使い、Googleの承認サーバーURLに含めるリクエストパラメータを設定し、最後に createAuthUrl() でそのURLを表示している。

setClientId()setClientSecret() には、Google Cloud Platform上で作成したOAuthクライアントIDとクライアントシークレットをそれぞれセットする。

setRedirectUri() には、Googleの承認サーバーが承認コードを返す先を指定する。urn:ietf:wg:oauth:2.0:oob を指定すると、コードはブラウザのタイトルバーに返される。
※実際はGoogleの承認サーバーのページ上にも表示される(後述)

setAccessType() で、リクエストパラメータ access_type の値をセットする。offline は、Googleの承認サーバーがアクセストークンを返す際に一緒に更新トークンも返すことを指定するもの(後述)。

setScopes() で、リクエストパラメータ scope の値をセットする。https://www.googleapis.com/auth/youtube は、Google Cloud Platform上のOAuth同意画面の設定で追加したスコープ ./auth/youtube に該当する。

上記コードを実行すると下記のようなURLが表示される。

https://accounts.google.com/o/oauth2/auth?response_type=code&access_type=offline&client_id=xxx&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&state&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube&approval_prompt=auto

このURLにWebブラウザでアクセスし、承認コードを取得する。

  1. アクセス権を付与するアカウントを選択
  2. "許可" をクリック
    ※「このアプリは確認されていません」と表示される場合(OAuth同意画面の設定でユーザーの種類に "外部" を選択した場合)
    • 左下の "詳細を表示" をクリック
    • "<OAuth同意画面の設定で入力したアプリケーション名>(安全ではないページ)に移動" をクリック
    • "許可" をクリック
  3. 「このコードをコピーし、アプリケーションに切り替えて貼り付けてください。」とページ上に表示されている承認コードをコピー

続いて、取得した承認コードを使ってアクセストークンを取得する。


require_once '/path/to/google-api-php-client/vendor/autoload.php';

$CLIENT_ID = '_CLIENT_ID_';
$CLIENT_SECRET = '_CLIENT_SECRET_';
$REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob';
$CODE = '_CODE_'; // ここに承認コードを貼り付ける
$TOKEN_FILENAME = "token.json";

$client = new Google_Client();
$client->setClientId($CLIENT_ID);
$client->setClientSecret($CLIENT_SECRET);
$client->setRedirectUri($REDIRECT_URI);
$client->setAccessType('offline');
$client->setScopes('https://www.googleapis.com/auth/youtube');

$tokenArray = $client->fetchAccessTokenWithAuthCode($CODE);
$tokenJson = json_encode($tokenArray);
file_put_contents($TOKEN_FILENAME, $tokenJson);

$youtube = new Google_Service_YouTube($client);
// 以降、$youtubeを使ってAPIリクエストを行える

fetchAccessTokenWithAuthCode($CODE) で承認コードを使ってアクセストークンを取得する。このメソッドは短期間有効なアクセストークンと更新トークンを含む配列を返す。
※承認コードを取得する際、リクエストパラメータ access_typeoffline を指定したので更新トークンも取得されることに留意

このトークンはAPIリクエストを行う際に毎回必要になるので、今後の開発テスト実行時のためにJSON形式でファイルに保存しておく。
※トークンを保存しておかないと、OAuth 2.0の同意フローを再度行ってアクセストークンを取得しなければならない


今後の実行時には、ファイルに保存したトークンのリストア、また、アクセストークンは定期的に期限切れになるので、更新トークンを使った新しいアクセストークンの取得が必要になる。

下記がそれを行うコードになる。


require_once '/path/to/google-api-php-client/vendor/autoload.php';

$CLIENT_ID = '_CLIENT_ID_';
$CLIENT_SECRET = '_CLIENT_SECRET_';
$REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob';
$CODE = '_CODE_';
$TOKEN_FILENAME = "token.json";

$client = new Google_Client();
$client->setClientId($CLIENT_ID);
$client->setClientSecret($CLIENT_SECRET);
$client->setRedirectUri($REDIRECT_URI);
$client->setAccessType('offline');
$client->setScopes('https://www.googleapis.com/auth/youtube');

$tokenJson = file_get_contents($TOKEN_FILENAME);

$client->setAccessToken($tokenJson);

if ($client->isAccessTokenExpired())
{
  $newTokenArray = $client->fetchAccessTokenWithRefreshToken();
  $newTokenJson = json_encode($newTokenArray);
  file_put_contents($TOKEN_FILENAME, $newTokenJson);
}

$youtube = new Google_Service_YouTube($client);
// 以降、$youtubeを使ってAPIリクエストを行える

ファイルから読み込んだJSON形式のトークン情報は、setAccessToken() を使ってクライアント上にリストアできる。

アクセストークンの期限切れは isAccessTokenExpired() で確認できる。期限切れの場合は、fetchAccessTokenWithRefreshToken() を使う。このメソッドにより、更新トークンを使って新しいアクセストークンが取得することができる。

トークンが更新された場合、また次回実行時のためにJSON形式でファイルに保存する。


最後に、上記3つのコードを OAuthClient クラスとして書き直したものが下記。今後、実際に利用していくのはこのコードになる。


require_once '/path/to/google-api-php-client/vendor/autoload.php';

$CLIENT_ID = '_CLIENT_ID_';
$CLIENT_SECRET = '_CLIENT_SECRET_';
$REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob';
$TOKEN_FILENAME = "token.json";
$CODE = '_CODE_';


class OAuthClient
{
  const EMPTY_CODE = 1;
  const INVALID_CODE = 2;
  const INVALID_TOKEN_FILE = 3;

  private $client;
  private $code;
  private $tokenFileName;

  public function __construct(Google_Client $client, $clientId, $clientSecret, $redirectUri, $tokenFileName = "token.json", $code = "")
  {
    $this->client = $client;
    $this->client->setClientId($clientId);
    $this->client->setClientSecret($clientSecret);
    $this->client->setRedirectUri($redirectUri);
    $this->client->setAccessType('offline');

    $this->tokenFileName = $tokenFileName;
    $this->code = $code;
  }

  public function setScopes($scope_or_scopes)
  {
    $this->client->setScopes($scope_or_scopes);
  }

  public function getAuthUrl()
  {
    return $this->client->createAuthUrl();
  }

  public function getAuthorizedClient()
  {
    if (empty($this->code))
    {
      throw new Exception("Get a code. (Remove the \"{$this->tokenFileName}\" file if it exists.)", self::EMPTY_CODE);
    }

    if (!file_exists($this->tokenFileName))
    {
      $tokenArray = $this->client->fetchAccessTokenWithAuthCode($this->code);

      if (!isset($tokenArray['access_token']))
      {
        throw new Exception("Invalid code. Get a new code.", self::INVALID_CODE);
      }

      $this->storeAccessToken($tokenArray);
    }
    else
    {
      $this->restoreAccessToken($this->tokenFileName);
    }

    return $this->client;
  }

  private function restoreAccessToken()
  {
    $tokenJson = file_get_contents($this->tokenFileName);
    $tokenArray = json_decode($tokenJson, true);

    if (!isset($tokenArray['access_token']))
    {
      throw new Exception( "Invalid file: \"{$this->tokenFileName}\". Remove the file, then execute the program again.", self::INVALID_TOKEN_FILE);
    }

    $this->client->setAccessToken($tokenJson);

    if ($this->client->isAccessTokenExpired())
    {
      $newTokenArray = $this->client->fetchAccessTokenWithRefreshToken();
      $this->storeAccessToken($newTokenArray);

      fwrite(STDERR, "# " . __METHOD__ . ": Token updated.\n");
    }
  }

  private function storeAccessToken(array $token)
  {
    $tokenJson = json_encode($token);
    file_put_contents($this->tokenFileName, $tokenJson);

    fwrite(STDERR, "# " . __METHOD__ . ": Token stored.\n");
  }
}


try
{
  $gclient = new Google_Client();
  $oauth = new OAuthClient($gclient, $CLIENT_ID, $CLIENT_SECRET, $REDIRECT_URI, $TOKEN_FILENAME, $CODE);
  $oauth->setScopes('https://www.googleapis.com/auth/youtube');
  $client = $oauth->getAuthorizedClient();
}
catch(Exception $e)
{
  switch($e->getCode())
  {
    case OAuthClient::EMPTY_CODE:
    case OAuthClient::INVALID_CODE:
      echo $e->getMessage() . "\n";
      echo $oauth->getAuthUrl() . "\n";
      exit;
    case OAuthClient::INVALID_TOKEN_FILE:
      echo $e->getMessage() . "\n";
      exit;
    default:
      throw $e;
  }
}

$youtube = new Google_Service_YouTube($client);
// 以降、$youtubeを使ってAPIリクエストを行える