【GAS/GCP】Google Cloud の API をサービスアカウントとして使用する方法

# 手順
## API の有効化
### 手順1
[Google Cloud](https://console.cloud.google.com/) に行き、左上の**メニュー**から「**API とサービス**＞**ライブラリ**」を選択する。
（事前に [Google Cloud](https://console.cloud.google.com/) でプロジェクトを作成しておく必要がある）

<img width="500" alt="TSUBASAMUSU.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670397/968d5ef7-760f-72cc-afcb-92e54d09c44e.png">

### 手順2
使用したい API を探して「**有効にする**」を押す。

<img width="500" alt="TSUBASAMUSU.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670397/030a358b-5753-1d73-3cc8-fc3eb26b89cb.png">

## サービスアカウントの作成
### 手順3
左上の**メニュー**から「**IAM と管理**＞**サービスアカウント**＞**サービスアカウントを作成**」を押す。

<img width="500" alt="TSUBASAMUSU.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670397/77073b38-4b00-4cad-e2c9-cd2c74250063.png">

### 手順4
**サービスアカウント名**や**サービスアカウントの説明**、**ロール**、**IAM の条件**、**サービスアカウントユーザーロール**、**サービスアカウント管理者ロール**等を設定し終えたら、作成した**サービスアカウント**を選択して「**キー**＞**鍵を追加**＞**新しい鍵を作成**」を押す。

<img width="500" alt="TSUBASAMUSU.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670397/5ccb99ea-399d-4ea8-7642-e51360ef1700.png">

### 手順5
「**キーのタイプ**」が「**JSON**」になっている事を確認して右下の「**作成**」を押す。

<img width="500" alt="TSUBASAMUSU.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670397/98f122b8-d872-2ae3-bf52-7d8475ec7574.png">

## コーディング
### 手順6
**Google Apps Script** を開き、下記のコードをコピペする。

```js:Sample.gs
function getGoogleCloudJwt() 
{
  const PRIVATE_KEY = "プライベートキー";

  const SCOPES =
  [
    "必要なスコープの URL"
  ];

  const  HEADERS =
  {
    alg: "RS256",
    typ: "JWT"
  };

  const NOW = Math.floor(Date.now() / 1000);

  const CLAIM =
  {
    iss: "サービスアカウントのメールアドレス",
    scope: SCOPES.join(" "),
    aud: "https://oauth2.googleapis.com/token",
    exp: NOW + 3600,
    iat: NOW
  };

  const ENCODED_HEADERS = Utilities.base64EncodeWebSafe(JSON.stringify(HEADERS));

  const ENCODED_CLAIM = Utilities.base64EncodeWebSafe(JSON.stringify(CLAIM));

  const SIGNATURE = Utilities.computeRsaSha256Signature(ENCODED_HEADERS + "." + ENCODED_CLAIM, PRIVATE_KEY);

  const OPTIONS =
  {
    method: "post",
    contentType: "application/x-www-form-urlencoded",
    payload:
    {
      grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
      assertion: ENCODED_HEADERS + "." + ENCODED_CLAIM + "." + Utilities.base64EncodeWebSafe(SIGNATURE)
    }
  };

  const RESPONSE = JSON.parse(UrlFetchApp.fetch("https://oauth2.googleapis.com/token", OPTIONS).getContentText());

  return RESPONSE.access_token;
}
```
### 手順7
「手順6」で記述したコードの**プライベートキー**と**サービスアカウントのメールアドレス**をそれぞれ「手順5」で作成した JSON ファイル内の「**private_key**」と「**client_email**」の値に変更する。
（**プライベートキー**には「-----BEGIN」で始まり、 「END PRIVATE KEY-----\n」で終わる値をそのまま入れる）
```json:SampleServiceAccount.json
{
  "type": "service_account",
  "project_id": "xxxx-xxxxxx",
  "private_key_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "private_key": "-----BEGIN PRIVATE KEY-----\nxxxxxxxx\n-----END PRIVATE KEY-----\n",
  "client_email": "xxxx@-xxxxxx.iam.gserviceaccount.com",
  "client_id": "xxxxxxxxxxxxxxxxxxxxx",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/xxxx%40xxxx-xxxxxx.iam.gserviceaccount.com",
  "universe_domain": "googleapis.com"
}
```
### 手順8
[こちら](https://developers.google.com/identity/protocols/oauth2/scopes)を参考にして、「手順6」で記述したコードの「**必要なスコープの URL**」に使用する API に応じたスコープの URL を記述する。
```js:Sample.gs
function getGoogleCloudJwt() 
{
  const PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\nxxxxxxxxx\n-----END PRIVATE KEY-----\n";

  const SCOPES =
  [
    "https://www.googleapis.com/auth/cloud-platform.read-only"//今回は Cloud Storage 上のオブジェクトを読み取る
  ];

  const  HEADERS =
  {
    alg: "RS256",
    typ: "JWT"
  };

  const NOW = Math.floor(Date.now() / 1000);

  const CLAIM =
  {
    iss: "xxxx@xxxx-xxxxxx.iam.gserviceaccount.com",
    scope: SCOPES.join(" "),
    aud: "https://oauth2.googleapis.com/token",
    exp: NOW + 3600,
    iat: NOW
  };

  const ENCODED_HEADERS = Utilities.base64EncodeWebSafe(JSON.stringify(HEADERS));

  const ENCODED_CLAIM = Utilities.base64EncodeWebSafe(JSON.stringify(CLAIM));

  const SIGNATURE = Utilities.computeRsaSha256Signature(ENCODED_HEADERS + "." + ENCODED_CLAIM, PRIVATE_KEY);

  const OPTIONS =
  {
    method: "post",
    contentType: "application/x-www-form-urlencoded",
    payload:
    {
      grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
      assertion: ENCODED_HEADERS + "." + ENCODED_CLAIM + "." + Utilities.base64EncodeWebSafe(SIGNATURE)
    }
  };

  const RESPONSE = JSON.parse(UrlFetchApp.fetch("https://oauth2.googleapis.com/token", OPTIONS).getContentText());

  return RESPONSE.access_token;
}
```
### 手順9
API を使用するコードの**ヘッダー**の ``Authorization`` のトークンの部分を``getServiceAccountOAuthToken()``に変更する。
```js:Sample.gs
function getCloudStorageObject()
{
  const HEADERS =
  {
    "Content-Type": "application/x-www-form-urlencoded",//使用する API に応じて変更
    "Authorization": "Bearer " + getGoogleCloudJwt()//「getGoogleCloudJwt()」に変更
  };

  const REQUEST =
  {
    method: "get",//使用する API に応じて変更
    muteHttpExceptions : true,
    headers: HEADERS
  };

  const API_URL = "https://storage.googleapis.com/storage/v1/b/bucket/o/object";//使用する API に応じて変更

  const RESPONSE = UrlFetchApp.fetch(API_URL, REQUEST);

  return RESPONSE.getBlob();//行いたい処理に応じて変更
}
```
## デバッグ
### 手順10
「**▷実行**」を押して正常に処理が走る事を確認する。

<img width="500" alt="TSUBASAMUSU.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670397/13df7b4c-fe54-a744-098a-4009b0ac0a33.png">

# その他
## 参考記事
https://diary.syarihu.net/2020/03/qiita-gasandroid.html

https://cloud.google.com/storage/docs/json_api/v1/objects/get

https://qiita.com/plumfield56/items/1c5ab661a598c433c5a7

https://developers.google.com/identity/protocols/oauth2/scopes

# 最後に
## 株式会社ModelingXについて
<img width="200" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670397/4261f1c0-a380-01a9-9c69-08a836c6f02f.jpeg">

富山県を拠点とするスタートアップ企業、ModelingXは、2022年4月に創業し、「テクノロジーで感動体験を提供する」というミッションを掲げています。当社が開発中の暮らし体験メタバース「MELIFE（ミライフ）」は、バーチャル住宅展示場や銀行相談、家具購入、婚活などができる「地域単位の新たな経済圏」を目指しています。

↓サービスサイトはこちらから↓

https://melife.io/

ModelingXはリモートワーク＆フレックスタイム制を完備。
UnityやUEを共に探求する仲間を全国から募集しています。

↓企業詳細はこちら↓

https://modelingx.jp/