はじめに
NodeでGoogle Drive等を使うアプリを開発しているのですが、Nodeのクライアントを使いこなすまでに苦労しました。 特にGoogleアカウントへのアクセス許可をする方法はOAuthばかり出てきます。私は自分のアカウントだけアクセス出来れば良いので、Server-to-serverで良かったのですが、良い記事が見つからなかったので情報を共有しておきます。なお、Server-to-serverのオフィシャルドキュメントはこちらにあります。
前提
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で作成できます。
ここでは次の図のように新規作成します。なお、環境によっては既にService Accountが用意されています。今回はどんな環境でも同じように出来るように、新規作成方法を記録しておきます。RoleはProject->Ownerにして下さい(ここ大事)。
JSONを選択してCreateするとキーファイルがダウンロードされます。これは外部に漏れないように大事に保存して下さい。
サービスアカウントを作成するとemailが割り当てられます。これは後でログインの確認に使います。
NodeによるDrive APIの動作確認
これ以降はNodeでの確認方法です。サンプルプログラムなどはGithubに上がっていますので、こちらを見てもらった方が早いかもしれません。ちなみにローカルのWindowsマシンにcloneして動作する事を確認しています。
セットアップ
まずnpmで必要なモジュールをインストールします。
$ mkdir myProject
$ cd myProject
$ npm init -y
$ npm install googleapis dotenv fs
googleapisが公式のクライアントライブラリです。
次にダウンロードしておいたキーファイルを.privateフォルダに置きます。
$ mkdir .private
$ cp /path/to/credential.json ./private/
# gitを使っている場合は.gitignoreに.privateフォルダを入れる
$ echo '.private' >> .gitignore
うっかりgithubなどにキーファイルをアップロードしないように、.privateフォルダは.gitignoreに入れておいてください。
さて、このキーをどうやって使うかですが、
公式マニュアルによると、環境変数の利用が推奨されています。次のように環境変数でキーファイルのパスを指定しておくと、Google公式クライアントは認証を勝手にやってくれるそうです。
export GOOGLE_APPLICATION_CREDENTIALS='/path/to/myProject/.private/credential.json'
ただし、exportコマンドでセットアップするだけだと、コンソールからログアウトすると変数がきえてしまいます。従って今回は.envファイルを利用しました。.envファイルに定義した変数はランタイムにロードします(詳細後述)。
echo 'GOOGLE_APPLICATION_CREDENTIALS=./.private/credential.json' >> .env
Node clientを使ったDrive API実行
さて、ここから実際のプログラムの話です。サンプルプログラムはGithubに上がっています。ここでは認証に関するポイントだけ書いておきますので、ファイルのダウンロードなどはGithubを確認して下さい。私自身はGoogleによるexampleもを参考にしました。
まず.envの環境変数を読み出す為にdotenvを使います。これでexportコマンド実行と同じ効果があります。
'use strict';
require('dotenv').config();
Google driveクライアントを取得するには次のようにします。SCOPESというのはClientのAuthorization設定です。ここではdriveのreadonly設定しています。スコープの詳細はこちらを参照してください。クライアントは最新のV3を使います。
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が正しく返ってくるか確認して下さい。
const auth_email = drive._options.auth.email;
console.log("iam: " + auth_email);
stackoverflowとか見ていると、service accountのemailをDrive側でshare設定しないとダウンロード出来ないなどと書いてありますが、そんな事はありませんでした(Sheet APIを使う時はshare設定が必要になります)。
Driveのfile Id取得
サンプルプログラム ではDriveのFile IDを使ってダウンロードするファイルを指定しています。記事作成時点(2019年6月)では次の方法で取得しました。
- Google driveにアクセス
- ダウンロードしたいファイルを右クリック
- 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でファイルIDが間違っていないか確認しました。Google App ScriptはService accountによる認証などは不要なのでより簡単にファイルへのアクセスが出来ます。
Gmail APIについて
残念ながらこの記事の方法ではGmail APIを使ったメール送信は出来ません。厳密にはGsuiteアカウントでアドミン権限を持っていないとGmail APIにアクセスする権限を与える事が出来ません。この辺りはGoogleのドキュメントに書かれています。私はNodemailerを使ってメール送信しています。
Storage APIについて
google-api-node-clientのstorageのexampleを見ても何も書いてありません。
リンクを辿っていくとnodejs-storageというクライアントが見つかります。
Storage APIは使えそうですが、私はfirebase storageを使っています。firebaseの方がauthentificationは簡単ですし、backendはgoogle cloud storageなのでサーバーサイドでクライアントを簡単に取得出来ます。
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が返ってきますので、このオブジェクトを使ってStorageにアクセス出来ます。例えば次のプログラムは上記clientのuploadのマニュアルを参考にして書いたプログラムです。マニュアルと比較すると、そのままなのが分かります。
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だと色々と簡単に出来るので、要件によってはそちらで済ませるのがおススメです。