NodeでGoogle Drive, Cloud Storage, Gmailを使ったアプリ開発（主に認証)

# はじめに
NodeでGoogle Drive等を使うアプリを開発しているのですが、Nodeのクライアントを使いこなすまでに苦労しました。 特にGoogleアカウントへのアクセス許可をする方法はOAuthばかり出てきます。私は自分のアカウントだけアクセス出来れば良いので、Server-to-serverで良かったのですが、良い記事が見つからなかったので情報を共有しておきます。なお、Server-to-serverのオフィシャルドキュメントは[こちら](https://cloud.google.com/docs/authentication/production#obtaining_and_providing_service_account_credentials_manually
)にあります。

# 前提

GoogleアカウントでGoogle cloudのプロジェクトを作成してDrive APIを有効にしておいてください。

APIの使い方の説明はnodeおよびnpmはインストールしている事を前提としています。私はWindows10で開発してUbuntuでも動作確認しています。
- node v8.9.4
- npm  v5.6.0

なお、この記事でのコマンドの例は全てLinuxです。

# Service Account作成

まずはGoogle Cloudへのアクセス権限をアプリが取得する為のService Accountを作成します。Google DriveではなくGoogle Cloudです。つまり、Drive以外にもアクセス出来るようになる（はず）です（この期待は裏切られるのですが）。イメージとしては自分のアカウントにアクセス出来るProxyアカウントです。これはGoogle CloudコンソールのAPI&ServiceのCredentialsで作成できます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/29999/5d2982a9-04ee-3e43-a93f-ae7a2d69d4e8.png)

ここでは次の図のように新規作成します。なお、環境によっては既にService Accountが用意されています。今回はどんな環境でも同じように出来るように、新規作成方法を記録しておきます。RoleはProject->Ownerにして下さい(ここ大事)。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/29999/8b365be2-0d00-08aa-9145-ed666422235f.png)

JSONを選択してCreateするとキーファイルがダウンロードされます。これは外部に漏れないように大事に保存して下さい。

サービスアカウントを作成するとemailが割り当てられます。これは後でログインの確認に使います。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/29999/c02d98df-4fd6-d945-04b7-a663a01fd5cc.png)

# NodeによるDrive APIの動作確認

これ以降はNodeでの確認方法です。サンプルプログラムなどは[Github](https://github.com/plsplsme/GoogleDriveApiNodeMinProject)に上がっていますので、こちらを見てもらった方が早いかもしれません。ちなみにローカルのWindowsマシンにcloneして動作する事を確認しています。

## セットアップ
まずnpmで必要なモジュールをインストールします。

```bash
$ mkdir myProject
$ cd myProject
$ npm init -y
$ npm install googleapis dotenv fs
```
googleapisが公式のクライアントライブラリです。
次にダウンロードしておいたキーファイルを.privateフォルダに置きます。

```bash
$ mkdir .private
$ cp /path/to/credential.json ./private/

# gitを使っている場合は.gitignoreに.privateフォルダを入れる
$ echo '.private' >> .gitignore
```

うっかりgithubなどにキーファイルをアップロードしないように、.privateフォルダは.gitignoreに入れておいてください。

さて、このキーをどうやって使うかですが、
[公式マニュアル](https://cloud.google.com/docs/authentication/production#obtaining_and_providing_service_account_credentials_manually)によると、環境変数の利用が推奨されています。次のように環境変数でキーファイルのパスを指定しておくと、Google公式クライアントは認証を勝手にやってくれるそうです。

```
export GOOGLE_APPLICATION_CREDENTIALS='/path/to/myProject/.private/credential.json'
```

ただし、exportコマンドでセットアップするだけだと、コンソールからログアウトすると変数がきえてしまいます。従って今回は.envファイルを利用しました。.envファイルに定義した変数はランタイムにロードします(詳細後述)。

```bash
echo 'GOOGLE_APPLICATION_CREDENTIALS=./.private/credential.json' >> .env
```

## Node clientを使ったDrive API実行

さて、ここから実際のプログラムの話です。サンプルプログラムは[Github](https://github.com/plsplsme/GoogleDriveApiNodeMinProject)に上がっています。ここでは認証に関するポイントだけ書いておきますので、ファイルのダウンロードなどはGithubを確認して下さい。私自身はGoogleによる[example](https://github.com/googleapis/google-api-nodejs-client/tree/master/samples/drive)もを参考にしました。

まず.envの環境変数を読み出す為にdotenvを使います。これでexportコマンド実行と同じ効果があります。

```javascript
'use strict';
require('dotenv').config();
```
Google driveクライアントを取得するには次のようにします。SCOPESというのはClientのAuthorization設定です。ここではdriveのreadonly設定しています。[スコープの詳細はこちら](https://developers.google.com/drive/api/v3/about-auth)を参照してください。クライアントは最新のV3を使います。

```javascript
const getDrive = async () => {
  const auth = await google.auth.getClient({
    scopes: ['https://www.googleapis.com/auth/drive.readonly']
  });

  const drive = await google.drive({ version: 'v3', auth: auth });
  return drive;
}
```

Service accountへのログインが出来たかを確認するには、次のようにauth emailを確認します。
自分の作ったservice acccountのemailが正しく返ってくるか確認して下さい。

```javascript
const auth_email = drive._options.auth.email;
console.log("iam: " + auth_email);
```

stackoverflowとか見ていると、service accountのemailをDrive側でshare設定しないとダウンロード出来ないなどと書いてありますが、そんな事はありませんでした（Sheet APIを使う時はshare設定が必要になります）。

## Driveのfile Id取得

[サンプルプログラム](https://github.com/plsplsme/GoogleDriveApiNodeMinProject) ではDriveのFile IDを使ってダウンロードするファイルを指定しています。記事作成時点（２０１９年６月）では次の方法で取得しました。

1. Google driveにアクセス
2. ダウンロードしたいファイルを右クリック
3. Get shareable link をクリック

上記のリンクは
https://drive.google.com/open?id=FILE-ID-OF-GOOGLE-DRIVE
のような形式になっています。FILE-ID-OF-GOOGLE-DRIVEがファイルIDです。共有設定は忘れないようにOFFしておいてください。

## GASによる確認

プログラム作成時はファイルのダウンロードが出来なかったり、色々と迷いました。特にfileIdが間違って無いだろうか？とかの不安があったので、この時は[Google App Script](https://developers.google.com/apps-script/reference/drive/drive-app#getFileById(String))でファイルIDが間違っていないか確認しました。Google App ScriptはService accountによる認証などは不要なのでより簡単にファイルへのアクセスが出来ます。

# Gmail APIについて
残念ながらこの記事の方法ではGmail APIを使ったメール送信は<b>出来ません</b>。厳密にはGsuiteアカウントでアドミン権限を持っていないとGmail APIにアクセスする権限を与える事が出来ません。この辺りは[Googleのドキュメント](https://developers.google.com/identity/protocols/OAuth2ServiceAccount)に書かれています。私は[Nodemailer](https://nodemailer.com/about/)を使ってメール送信しています。

# Storage APIについて

google-api-node-clientのstorageのexampleを見ても何も書いてありません。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/29999/523d991c-9f2b-fce5-c3e4-f510caccbde4.png)

リンクを辿っていくと[nodejs-storage](https://github.com/googleapis/nodejs-storage)というクライアントが見つかります。

Storage APIは使えそうですが、私はfirebase storageを使っています。firebaseの方がauthentificationは簡単ですし、backendはgoogle cloud storageなのでサーバーサイドでクライアントを[簡単に取得](https://firebase.google.com/docs/storage/admin/start)出来ます。


```javascript
var admin = require("firebase-admin");
var serviceAccount = require("path/to/serviceAccountKey.json");

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    storageBucket: "<BUCKET_NAME>.appspot.com"
});
var bucket = admin.storage().bucket();
```

このbucketはGoogle cloud storageのNode.js clientの[bucket](https://cloud.google.com/nodejs/docs/reference/storage/2.0.x/)が返ってきますので、このオブジェクトを使ってStorageにアクセス出来ます。例えば次のプログラムは上記clientの[uploadのマニュアル](https://cloud.google.com/nodejs/docs/reference/storage/2.0.x/Bucket#upload)を参考にして書いたプログラムです。マニュアルと比較すると、そのままなのが分かります。

```javascript
  const upload = (localPath, serverPath, fileName) => {
    return new Promise((resolve, reject) => {
      const options = {
        destination: `${serverPath}/${fileName}`
      };
      bucket.upload(`${localPath}/${fileName}`, options, (err, file) =>{
        if (err) {
          reject(err);
          return;
        }
        console.log('upload success');
        resolve(file);
      });
    })
  }
```

# まとめ

この記事ではGoogleApiでのServer-to-serverによる認証について主に書きました。GoogleApiを使えば何でも出来るわけでは無いので色々と面倒なのが少し残念です。GASだと色々と簡単に出来るので、要件によってはそちらで済ませるのがおススメです。