OAuth2.0は簡単に言うと、アクセストークンを用いて、パスワードやIDを入力することなくアプリケーションを外部のアプリケーションと連携することができる仕組みである。

OAuth2.0自体は、以下の動画と画像引用させていただいた記事がとてもわかりやすく解説されている。

https://qiita.com/TakahikoKawasaki/items/200951e5b5929f840a1f
https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be

今回は上記の図を参照しながら Google APIs で OAuth2.0 を使ったときの流れをまとめることで、自分の理解を深めようとする趣旨の記事を書いてみる。
完全に自分のための記事なので、より詳しく正確な内容は上記の記事などを参照してほしい。

OAuth2.0クライアントIDの作成

以下の画面からOAuth2.0クライアントIDを作成する。

認証情報の種類

ちなみにAPIキー・OAuth2.0・サービスアカウントの3種類があるが、これら3つの認証情報は、APIで取得するデータの種類とアクセス方法によって使い分ける必要がある。

認証の種類データの種類データのアクセス方法
APIキー公開データ匿名ユーザー
OAuth2.0公開/非公開データユーザーアカウント
サービスアカウント公開/非公開データサービスアカウント
参考:https://messefor.hatenablog.com/entry/2020/10/08/080414

APIキーは一般公開されているデータで、利用者がログインする必要のない(ユーザーを分ける必要がない)場合に利用する。
たとえば、Google Maps など。

OAuth2.0は特定の利用者をしぼる必要(ログインする必要)があり、公開/非公開のデータを取得・操作する際に利用する。
たとえば、Google Business Profile など。

サービスアカウントは複数のアカウント間で公開/非公開のデータを取得・操作する際に利用する。
たとえば、サービスアカウントに登録されているメールアドレスの個人のGoogleカレンダーをサービスアカウント経由でアプリケーションから操作したりすることができる。

認証済みのURLの設定とJSONファイルのダウンロード

APIを使うことを許可するURLを設定する。
JavaScript生成元はクライアントサイドスクリプトでAPIを使用する際に認可するURIで、リダイレクトURIはサーバーサイドプログラムでAPIを使用する際に認可するURIを指定する。
これらは基本的に同じURIでよい。

これらを指定すると、クライアントIDとクライアントシークレットなどの認証情報がはいったJSONが取得できる(クライアントIDとクライアントシークレットはこの画面でも確認ができるが、スクショでは削除している)。

これをもとにリフレッシュトークンを取得する。

リフレッシュトークンの取得

Google APIs のOAuth2.0では、APIの認可に必要なアクセストークンは短期間の有効期限が設定されているため、一定時間ですぐに使用不可となってしまう。
リフレッシュトークンは、そのアクセストークンを再発行する際に必要なトークンのことである。
リフレッシュトークンはそのトークン自体がリフレッシュされるのではなく、アクセストークンをリフレッシュするために必要なトークンであるため、文字面からはわかりづらいと思う。
なので、リフレッシュトークンは取得したものをずっと保存しておく必要があるし、リフレッシュトークンがないとアクセストークンを取得することができない。

リフレッシュトークンを取得するには、リフレッシュトークンを取得するために必要な認可コードを取得し、その認可コードを使ってリフレッシュトークンを利用するという手順を踏む必要がある。

まずは認可コードを取得するために以下のURLにブラウザでアクセスする。

https://accounts.google.com/o/oauth2/auth?
client_id=*********************&
redirect_uri=*********************&
scope=https://www.googleapis.com/auth/business.manage&
access_type=offline&
response_type=code

※scopeパラメーターは使用するアプリケーションによって異なる

その後、Google の同意画面が表示されるので、該当アカウントを選択すると、認証済みリダイレクトURIで設定したURIにパラメータが付与された以下のようなURLにリダイレクトされる。

http://example.com/?code=*********************&scope=*********************

このcodeパラメーターがリフレッシュトークンに必要な認可コードである。
この認可コードを使用してcurlコマンドを使用してリフレッシュトークンを取得する。

$ curl \
-d client_id=********************* \
-d client_secret=********************* \
-d redirect_uri=********************* \
-d grant_type=authorization_code \
-d code=********************* \
https://accounts.google.com/o/oauth2/token

すると、以下のようなフォーマットのJSONを取得することができる。

{
  "access_token": *********************,
  "expires_in": *********************,
  "scope": *********************,
  "token_type": "Bearer",
  "created": *********************,
  "refresh_token": *********************
}

このJSONにはアクセストークンとリフレッシュトークンの両方が含まれている。

APIを利用する際には、このJSONの内容をGoogleのクライアントライブラリが読み取り、適宜更新されるアクセストークンをリクエストヘッダーに含めデータを取得するなど諸々細かい処理をクライアントライブラリが担ってくれるため、あまりOAuth2.0の全体像が見えないが、そういう流れになっている(らしい)。

ひとまずこのJSONをファイル保存しておく。

サーバーサイドプログラムの作成

詳細な処理は利用するAPIによって異なるが、Google Business Profile API をPHPで利用する前提で大まかな処理の流れを書いていく。

前までの手順で認証情報のJSONとリフレッシュトークン・アクセストークンなどが記載されたJSONの2つのJSONファイルが取得できた。
前述のように、JSONをGoogleのクライアントライブラリはいい感じで読み取ってくれるため、直接このJSONの中の値は扱わない。
そのため、これをサーバー内のアクセス不可領域にアップロードしておく。
ここでは認証情報のJSONを「cer.json」、トークンのJSONを「token.json」として、サーバーサイドプログラムと同階層に設置した前提とすると、プログラムは以下のような感じになる。

参考:https://notes.sharesl.net/articles/2527/

// $ composer require google/apiclient:^x.xx
// などのようなかたちで、Google API Client をインストールしておく
require_once 'vendor/autoload.php';

// API クライアントを作成
$client = new Google\Client();
// アプリケーションの名前を入力(任意)
$client->setApplicationName( 'ウェブ アプリケーション' );
// スコープの設定
// 利用するAPIに応じたURLを記述(以下は Business Profile API)
$client->setScopes( [
  'https://www.googleapis.com/auth/plus.business.manage'
] );

// 認証情報のJSONを読み込み
$client->setAuthConfig( 'cer.json' );

// リフレッシュトークン用の設定
$client->setAccessType( 'offline' );
$client->setApprovalPrompt( 'force' );
  
// トークンファイルがある場合はそこからアクセストークンを取得
if ( file_exists( 'token.json' ) ) {
  $access_token = json_decode( file_get_contents( 'token.json' ), true );
  $client->setAccessToken($access_token);
}

// アクセストークンが期限切れの場合は再取得
if ( $client->isAccessTokenExpired() ) {
  // リフレッシュトークンを取得
  $refresh_token = $client->getRefreshToken();
  // 新しいアクセストークン(オブジェクト)を取得
  $client->fetchAccessTokenWithRefreshToken( $refresh_token );
  $access_token = $client->getAccessToken();
  // 新しいアクセストークン(オブジェクト)にリフレッシュトークンを追加
  // 最初に取得したトークンのJSONと同じフォーマットにし、リフレッシュトークンを永続的に保存するため
  $access_token[ 'refresh_token' ] = $refresh_token;
  // トークンの保存
  file_put_contents( 'token.json', json_encode( $access_token ) );
  // 新しいアクセストークンを利用
  $client->setAccessToken( $access_token );
}

// 以降はそれぞれのAPIに応じた処理を記述

おわりに

サーバーサイドのプログラムは、OAuth2.0を知らないと一見するとややこしいことをしているなという印象を持つと思う。
逆にOAuth2.0を知っていれば具体例として非常に模範的な実装だなという印象。