[Node.js] 2021年版サービス アカウント認証コード with ドメイン全体の委任

# はじめに

2020年10月に G Suite が Google Workspace にリブランドされました。
それとは全く関係ないですが Google Classroom や Google Chat などの Google Apis を Node.js 用クライアントである [googleapis](https://www.npmjs.com/package/googleapis) を使って呼び出す際に、サービスアカウントに対してドメイン全体の委任を行い OAuth 認証を行う必要があったのですが、情報を見つけるのに大分彷徨ってしまったり、見つけたコードの記法が少々古かったり、ポイントとなる `subject` の指定方法がやや不適切だったりしたので備忘を兼ねてコードを残しておきます。

# 認証コード

一応 googleapis のバージョンは 67.0.0 です。古いバージョン（39.2.0とか）でも動きます。

```JavaScript
const { google } = require('googleapis');
const credentials = require('./credentials.json'); // サービスアカウント秘密鍵jsonファイル
// ドメイン全体の委任で承認されている OAuth スコープの中から必要な分だけ指定する
const scopes = [
  'https://www.googleapis.com/auth/classroom.courses.readonly'
];
// API 呼出し主体となるアカウントを指定。無指定だとパーミッションエラーになる
const clientOptions = { subject: 'great-teacher@my-domain.com' };

async function main() {
  const auth = await google.auth.getClient({ credentials, scopes, clientOptions });
  const classroom = google.classroom({ version: 'v1', auth });

  // api 単位ではなくグローバルオプションとして認証情報をセットする場合は下記のようにする
  // const classroom = google.classroom({ version: 'v1' });
  // google.options({ auth });

  const response = await classroom.courses.list();
  console.log(JSON.stringify(response));
}

main().catch(err => {
  console.error(err);
  process.exit(1);
});
```

## ポイント

getClient のパラメーターとして `subject` を指定した `clientOptions` を渡す点が重要です。
これを指定しないと 403 エラー (`The caller does not have permission`) が返ってきます。
逆に指定した場合はそのアカウントで認証して API を呼び出した場合と同じ結果が返ってきます。
例えば Classroom において教師A が課題1～10、教師B が課題11～20 を作成していた場合、
`subject` に教師A のアカウントを指定して `courses.courseWork.list()` を実行すれば課題1～10が、
教師B のアカウントを指定して実行すれば課題11～20 が返ってきます。

また、ここでは `require` を使って秘密鍵 Json ファイルをオブジェクトとして読み込んで `credentials` パラメーターに渡していますが、下記のようにファイルパスを `keyFile` パラメーターに渡しても良いです。

```JavaScript
  const auth = await google.auth.getClient({ keyFile: './credentials.json', scopes, clientOptions });
```

# 付録：サービスアカウント作成とドメイン全体の委任

一応この記事だけ見れば良いようにサービスアカウント作成と、そのアカウントに対するドメイン全体の委任の手順を記載しておきます。ドメイン全体の委任についてはGoogle Workspaceの管理者しか実施できず、説明するためのスクショを撮るのもままならなかったりしますので。

## OAuth 同意画面の設定

`API とサービス` > `OAuth 同意画面`
ここで設定したアプリ名が Google 管理コンソールでドメイン全体の委任を設定した際に表示されます。

<details><summary>OAuth 同意画面 の設定手順</summary><div>

### OAuth 同意画面

下記のとおり設定し「作成」または「保存して次へ」

* UserType：目的に応じて選択
* アプリ情報
  * アプリ名：適切な名前を入力
  * ユーザーサポートメール：プルダウンから選択
  * アプリのロゴ：空でOK。ある場合は指定
* アプリのドメイン：全て空でOK。ある場合は指定
* 承認済みドメイン：追加なしでOK
* デベロッパーの連絡先情報
  * メールアドレス：適切に指定

![oauth-001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/8e7ae425-6281-61ba-a1b1-7fcf7eb97e47.png)
![oauth-002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/bba213ff-d580-9012-43e9-3ecbdb74cdf7.png)

### スコープ

なにも設定せず「保存して次へ」
![oauth-003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/35e9c488-70b3-447e-4dc3-ac516881bd31.png)

### テストユーザー

なにも設定せず「保存して次へ」
![oauth-004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/104abed0-97a1-1d83-6cce-fd7cb79bd8dc.png)
</div></details>

## サービスアカウントの準備

`API とサービス` > `認証情報`

<details><summary>サービスアカウントの準備手順</summary><div>

### サービスアカウント作成

`+ 認証情報の作成` から `サービス アカウント` を選択。
![serviceaccount-001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/d585ea23-8db5-bb3d-eb3e-bd74b5a6a4a7.png)
下記のとおり設定して「完了」

* サービス アカウント名：適当に（ここでは domain-delegation）
* サービス アカウントID：自動設定
* サービス アカウントの説明：空ないし任意の説明文

![serviceaccount-002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/66f0870a-6117-96f1-aa80-5ad74e82942f.png)

### ドメイン全体の委任を有効化

作成したサービスアカウントの編集ボタンをクリック。
![serviceaccount-003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/42f874bf-b464-6489-815d-d8c5fda865a7.png)

`ドメイン全体の委任の表示` をクリックして展開。
`G Suite ドメイン全体の委任を有効にする` にチェックを入れて「保存」
![serviceaccount-004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/2532528f-d550-88c9-0b8e-5ece219e5c60.png)

これにより「OAuth 2.0 クライアント ID」が表示されたことを確認し、再度サービスアカウントの編集ボタンをクリック。
![serviceaccount-005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/a69c895f-87bb-ebfb-d2b8-b7d18abc167c.png)

`鍵を追加` プルダウンから `新しい鍵を作成` をクリック。
キーのタイプとして「JSON」を選択して「作成」し、秘密鍵Jsonファイルをダウンロード。
このファイルがソースコード上で `require('./credentials.json')` により取り込んでいた json ファイル。
![serviceaccount-006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/54da2b66-c8e0-c86d-57c4-98459ed14828.png)
![serviceaccount-007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/6f43479d-cbc9-a449-0d88-8b3e4ad8e397.png)
![serviceaccount-008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/83485868-a64c-7313-08bd-fdeab51e7448.png)

このサービスアカウント用の OAuth クライアント ID は一覧のコピーボタンから取得する。
![serviceaccount-009.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/017b4a9e-4cc9-5874-615d-c37a3daa6847.png)
</div></details>

## ドメイン全体の委任の承認

[Google 管理コンソール](https://accounts.google.com/) > `セキュリティ`

<details><summary>ドメイン全体の委任の承認手順</summary><div>

セキュリティ項目一番下の `API の制御` パネルをクリック。
![workspace-001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/6e67070a-e7a2-78f8-4e31-764dfb0fa22b.png)

API の制御項目の一番下の `ドメイン全体の委任` にある「ドメイン全体の委任を管理」をクリック。
![workspace-002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/0cc14f51-26b8-5ab5-5b97-3947cda72c0a.png)

API クライアント一覧にある「新しく追加」をクリック。
![workspace-003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/99dd0434-0d2c-24ef-affb-ccd24fa1637d.png)

クライアント ID にサービスアカウントの OAuth クライアント ID、スコープに利用する Google APIs に必要なスコープを必要な数だけ入力して「承認」。
（カンマ区切りと書いてあるが、1 つ入力するたびにテキストボックスが増えていくので 1 つずつ入力すればよい）
![workspace-004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/bcc70fbf-1c53-6155-0486-b9a18cfc2edd.png)
![workspace-005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256480/f3b52f32-e08f-ff80-2540-2fa1bab2a1aa.png)
</div></details>

# おわりに

探し方が悪いのかサービスアカウント認証関連の公式ドキュメントを探しても `subject` を指定しなきゃダメという情報を見つけられず、参考にさせて頂いた記事を見てようやく認証を通すことができました。
一応 `subject` の指定方法は適切な形になっているはずですし、async/await 版でのサンプルにもなっていると思いますので参考になれば。

# 参考

* [GoogleAPI SDK ServiceAccount認証コード（Ruby or Node.js）](https://qiita.com/zaru/items/d0fb96ae5eae30002820)
* [Node.jsでGCPサービスアカウント使用したG Suiteアカウント情報取得の実装](https://www.niandc.co.jp/sol/tech/date20200309_1843.php)