GASから認証付きのCloud Functionsを実行する。

## はじめに
[Google Apps Script](https://developers.google.com/apps-script/reference)（以下GAS）から[Cloud Functions](https://cloud.google.com/functions/docs)（以下GCF）の認証付きのHTTP関数を呼べたら便利だなあという場面、あると思います。

本記事ではGASから認証付きHTTPトリガーのGCFを実行するための設定、記述方法についてご紹介します。

## GCPプロジェクト側の設定

GASのプロジェクトが作成されると初期設定では「デフォルト」というGCPプロジェクトと紐付けられています。
この紐付け先ののGCPプロジェクトをCloud Functionsがデプロイされているプロジェクトへ変更してあげる必要があります。
こちらの項では紐付け先のGCPプロジェクトの設定を行っていきます。

### OAuth同意画面の作成

OAuth同意画面とはGASを初回実行したときに表示されるポップアップウィンドウです。
OAuthを利用した認証するときによく出てくるあれです。
![スクリーンショット 2021-02-25 21.41.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94505/cdd9f557-5809-290c-4f17-3ee269f35655.png)

GCPのコンソールから「APIとサービス」->「OAuth同意画面」で設定を行うことができます。
既に作成されている場合はスキップでOKです。
まだ、作成されていない場合は「内部」あるいは「外部」向けに設定を進めてください。
GASの用途の多くは社内向けかと思いますのでGoogle Workspaceを利用している場合は「内部」でいいかと思います。

ひとまずは必須項目のみ設定しておき、必要に応じてスコープを追加していきましょう。（今回のデモでは特に必要ないです。）

### APIを有効化しておく。

続いてGASで利用されるGmail APIやDrive APIをGCPプロジェクト側で有効化しておく必要があります。
「APIとサービス」->「ライブラリ」からそのあたりのAPIを有効化しておきましょう。
![スクリーンショット 2021-02-25 21.50.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94505/a4d204e3-cfbf-cd63-7734-dc5898d0aa62.png)
Google Workspaceというカテゴリがあるのでフィルタをかけると便利です。

お疲れ様でした！
続いてGAS側の設定です。

## GAS側の設定

### プロジェクトの設定

先述のようにGASプロジェクトに紐付けられているGCPプロジェクトを先程設定したプロジェクトに変更する必要があります。
GASプロジェクトの設定画面から、「プロジェクトの変更」をクリックし、当該プロジェクト番号を貼り付けてください。
プロジェクト番号は、GCPコンソールのダッシュボードで確認できます。
また、後ほど`appscript.json`を編集するため、エディタで表示させておきます。
　![スクリーンショット 2021-02-25 21.55.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94505/e2d8c32f-5897-f04b-8175-39d074e4d0d2.png)

紐付け先GCPプロジェクトの認証情報画面でOAuthクライアントにGASのものが追加されていたらOKです。
![スクリーンショット 2021-02-25 22.24.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94505/f8468178-9537-6d6d-756b-db302d649690.png)


### appscript.jsonの編集

`appscript.json`では、GASのランタイムやタイムゾーン、OAuthスコープの設定を行うことができます。
GCFの認証つきHTTPトリガー関数ではエンドユーザの認証にID トークンを利用します。
このIDトークンをGASで取得するためにOAuthスコープを追加する必要があります。

```json:appscript.json
{
  "timeZone": "Asia/Tokyo",
  "dependencies": {
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
      "https://www.googleapis.com/auth/userinfo.profile",
      "https://www.googleapis.com/auth/userinfo.email",
      "https://www.googleapis.com/auth/script.external_request",
      "https://www.googleapis.com/auth/drive"
  ]
}
```

`https://www.googleapis.com/auth/userinfo.profile`と`https://www.googleapis.com/auth/userinfo.email`のスコープがIDトークン取得に必要なスコープです。

`https://www.googleapis.com/auth/script.external_request`はUrlFetchAppを利用する際に必要となるスコープです。
その他のスコープは必要に応じて追加してください。（スクリプト上のスコープが足りてない場合はその旨がエラーログで表示されます。）

GASでのIDトークン取得に関する公式ドキュメントは[こちら](https://developers.google.com/apps-script/reference/script/script-app#getidentitytoken)となります。

## 作ってみよう！

### Cloud Functionsをデプロイする。
認証付きのHTTPトリガーで関数を作っていきます。
![スクリーンショット 2021-02-25 22.29.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94505/23fab21a-8dc6-f801-4d59-2b9418337180.png)
ソースコードは簡単のためデフォルトのものにしておきます。

```js:index.js
/* ランタイムはNode.js 14(ベータ)です。 */
exports.helloWorld = (req, res) => {
  let message = req.query.message || req.body.message || 'Hello World!';
  res.status(200).send(message);
};
```

ソースが書けたらデプロイしましょう。
**既存の関数を使用する場合もOAuthクライアントの情報を反映させるために再度デプロイする必要がありますので注意してください！！**


>保護する関数を再度デプロイします。これにより、関数に正しいクライアント ID が設定されます。
https://cloud.google.com/functions/docs/securing/authenticating?hl=ja#google_sign-in

### GASを書いていく

先述のようにGASでIDトークンを取得するには次の関数を使用します。

```js
const idToken = ScriptApp.getIdentityToken()
```

上記で得られたIDトークンをHTTPリクエストに含めていきます。

```js:コード.gs
function myFunction() {
  const url = "https://xxxxxx.cloudfunctions.net/xxxxx"
  const res = UrlFetchApp.fetch(url, {
    headers: {
      Authorization: `Bearer ${ScriptApp.getIdentityToken()}`
    }
  })
  console.log(res.getContentText()) // Hello World!
}
```

お疲れ様でした！
正しく設定されていたら無事、認証付きのHTTPトリガーのGCFにアクセスできるかと思います。

## 最後に

Cloud Functionsの認証が思いのほか厄介だったのでまとめてみました。
GCP側でOAuth同意画面を用意したり、APIを有効化したりとGASの外での設定が多いため、
GASでエラーを吐くようであれば設定を確認してみてください。

紐付け先のGCPプロジェクトを変更して個人的にハマったのが、GASで発生した謎のエラー
**“We're sorry, a server error occurred. Please wait a bit and try again”**

原因はGCP側でDrive APIが有効化されてなかったことでした。

皆様もよきGAS＆GCFハックを！！