PHP GoogleClientを利用したOAuth認証でrefresh_tokenが取得できない場合の対応

PHPのGoogleClientを利用して、OAuth認証を行う場合にリフレッシュトークンが取得できずエラーになる場合がありました。

※ログイン時、アクセストークンは取得できるので直後は問題ないが、１時間程度経過するとエラーになる

エラーメッセージの例
* error: 'invalid_grant'
* error_description: 'Bad Request'
* refresh token must be passed in or set as part of setAccessToken

### 原因

1. アクセストークンは初回ログイン時のみ入手できる（再度ログインしても取れない）
    * アクセストークンが取得できるのは、ログイン時に「スコープの確認」が表示されたときだけ
    * スコープの確認は２回目以降表示されないため、初回を逃すと正しいプログラムでもリフレッシュトークンは取れない
2. アクセストークンを取得するには「AccessType: offline」かつ「ApprovalPrompt: force」である必要がある
3. 初回に間違いのあるプログラムを実行して入手できないと、プログラムを修正した後も取得できない

### 対応

対応方法①

* Googleからログオフして再度ログインが必要な状態にすればよい

※ログオフしても、ブラウザ(Chrome)側がログオン状態だとうまくいかない場合があったので、両方ログオフしてく
[Chrome からログアウトする](https://support.google.com/chrome/answer/9159867?hl=ja&co=GENIE.Platform%3DDesktop)


対応方法②

* ログオン時、毎回「スコープの確認」を表示させる
```php
        // 同意画面を毎回表示する(初回ログイン時以外でもリフレッシュトークンを取得できるようにする)
        $client->setPrompt('consent');
```

### サンプルコード

#### GoogleClientの生成
```php
    public function getClient($access_token='', $refresh_token='')
    {
        $client = new Google_Client();
        
        // 利用する機能(GCP側の設定(同意画面のスコープ)も必要)
        $scope = [
            Google_Service_Sheets::SPREADSHEETS,
        ];

        // 接続に必要な情報をセット
        $client->setClientId(self::GOOGLE_CLIENT_ID);
        $client->setClientSecret(self::GOOGLE_CLIENT_SECRET);
        $client->setRedirectUri(self::AUTH_CALLBACK_URL);
        $client->addScope($scope);
        $client->setAccessType('offline');
        $client->setApprovalPrompt('force'); // これがないとリフレッシュトークンが取得できない
        // 同意画面を毎回表示する(初回ログイン時以外でもリフレッシュトークンを取得できるようにする)
        $client->setPrompt('consent');

        if (!empty($access_token))
        {
            // ログイン前はtokenが空の状態で、ログイン画面にリダイレクト
            // ログインから戻ったタイミングで、tokenが取得できる（ので、それを保存する）
            // ログイン後は、保存したtokenを引数にセットすればCalenderやSpreadのAPIを呼ぶことができる
            $client->setAccessToken($access_token);

            // アクセストークンは60分で期限切れになるので、リフレッシュを行う
            if ( $client->isAccessTokenExpired() ) {
                $new_access_token = $client->fetchAccessTokenWithRefreshToken($refresh_token);
                $client->setAccessToken($new_access_token);
            }
        }

        return $client;
    }
```

#### ログイン
```php
    // googleAPIライブラリ生成
    $client = $this->MemberGooglePage->getClient();

    // Googleログインページへリダイレクト
    $auth_url = $client->createAuthUrl();
    $this->redirect($auth_url);
```

#### ログイン後のcallback

```php
    $client = $this->getClient();
    
    // auth_codeを元にアクセストークンを生成
    $accessToken = $client->fetchAccessTokenWithAuthCode($auth_code);
    
    // tokenの検証(ユーザー情報を取得)
    $userInfo = $client->verifyIdToken($accessToken['id_token']);

    // ログイン画面でセキュリティーの同意が表示された場合のみ、RefreshTokenが取得できる
    // ・デフォルトでは、セキュリティーの同意は初回ログイン時のみ表示
    $refresh_token = $client->getRefreshToken();
```


#### 取得したトークンの使い方
```php
    // googleClientに、tokenをセット
    $refresh_token = <DBやファイルに保存した値を読み込む>
    $access_token = <DBやファイルに保存した値を読み込む>
    $client = $this->getClient($access_token, $refresh_token);

    // スプレッドシートを新規に作成
    $service = new Google_Service_Sheets($client);
    $requestBody = new Google_Service_Sheets_Spreadsheet([
        'properties' => [
            'title' => 'スプレッドシートタイトル'
        ]
    ]);
    $response = $service->spreadsheets->create($requestBody);

    // 新規作成したスプレッドシートidを返す
    $spreadsheet_id = $response->spreadsheetId;
```