/ 2024.11.24

サーバーのデータを自分の Google Drive に自動的にバックアップをとるプログラムを作ろうと考え、Google Drive API を使って試していました。

ネットで情報を集めていたのですが、丁寧な情報があまりなくて苦労しました。

OAuth 2.0 認証を使う問題点

1. OAuth同意画面が必要になる

Google の API を使ったプログラムを日本語の Google 検索で調べると、認証に OAuth を使うというものばかりが検索結果に出てきます。

私はサーバーで少しだけ動かすプログラムを作るだけなので、人間が行う動作が必要ない、サーバーだけで完結できるサービスアカウントでの認証の方が良いだろうと思っていました。

OAuth を使うとアプリ(作ったプログラム)を使うユーザーに同意画面を出し、どのような権限がリクエストされるのかを表示し、同意を得なければなりません。

同意画面はブラウザで表示されるので、人間(私)がブラウザで認証のためのURLにアクセスしなくてはならないし、同意を得たらリダイレクトされるページを用意しなければなりません。

まぁリダイレクトされるページは localhost などの存在しないURLでもよくて、URLに付加されて渡される認証コードさえ分かればOKではあります。

どうしてみんな OAuth 認証を使っているのだろうと不思議に思っていたのですが、とりあえず説明通りにやってみると、Google Drive を使う際にちょっと癖があることが分かりました。

OAuth 認証でユーザーアカウントと連携をすると、そのユーザーのアカウントの Google Drive にデータ(今回はサーバーのバックアップファイル)を置けるのです。

私は OAuth 認証よりもサービスアカウントでの認証の方が良いだろうと思って、サービスアカウントで認証後にデータのバックアップを試してみたら、ファイルがバックアップされる場所がそのサービスアカウントの Google Drive でした。

つまりサービスアカウント認証では自分の個人用の Google Drive にファイルをバックアップすることができません。回避策があるのですが、一手間掛かります。

だからみんな OAuth 認証を使っているのかもしれません。

ちなみにサービスアカウントにも Google Drive のストレージ容量が与えられるというのは意外でした。使える容量をチェックしたら 15GB ほどもらえます。太っ腹ですね。

ただし、これは Google Cloud で説明されていない事実のため、サービスアカウントの Google Drive が永続するのか分かりません。バックアップしたファイルがいつか消えてしまうかも知れないという不安はあります。

2. アプリを公開しないと認証のトークンが失効する

OAuth を認証で使う場合、Google Cloud でアプリの公開ステータスを「テスト中」か「公開」かを選ばなくてはなりません。

私は単にサーバーのファイルをバックアップするだけですから、自分一人用のプログラムです。みんなに公開する必要がありません。「テスト中」を選びました。

サービスアカウントでの認証では自分の Google Drive にファイルがバックアップできないので、OAuth で認証をしてプログラムを作り終えました。

しかし数日後、Google API の認証が失敗してしまうようになりました。

Google\Service\Exception: {
  "error": "invalid_grant",
  "error_description": "Token has been expired or revoked."
}

「Token has been expired or revoked.」というエラーです。

Google API のサンプル通り、Access Token が失効したら Refresh Token で Access Token を再取得するようにしていたのですが、これがうまく動作しなかったのでしょうか?

でも調べてみると Refresh Token 周りの問題ではありませんでした。Stack Overflow で説明されていました。

'Token has been expired or revoked' - Google OAuth2 Refresh token gets expired in a few days - Stack Overflow

If your app is in testing mode then user tokens will expire in 7 days. Please find this explanations here: https://support.google.com/cloud/answer/10311615#zippy=%2Ctesting

つまり、アプリの公開ステータスが「テスト中」の状態で OAuth 認証を使っている場合、全ての Token が7日で失効するのです。

Google Cloud Platform のヘルプでこの仕様が説明されてますので見てみましょう。

Setting up your OAuth consent screen - Google Cloud Platform Console ヘルプ

Authorizations by a test user will expire seven days from the time of consent. If your OAuth client requests an offline access type and receives a refresh token, that token will also expire.

OAuth の同意画面で同意をしてから7日間しか認証が有効ではありません。Token の Refresh はできません。

これは知りませんでした。OAuth 認証鍵を作る際に事前に説明して欲しかったのですが…。Google Cloud は本当に分かりにくいです。

どう対処する?

ここで私が採れる選択肢は3つ。

  1. 7日ごとに新規に OAuth 同意画面から認証を行う
  2. アプリを公開する
  3. サービスアカウント認証にする

1つ目の「7日ごとに新規に OAuth 同意画面から認証をする」は面倒すぎてできません。

サーバーのバックアッププログラムですから、何もしなくても常時安定的にやってもらう必要があります。

2つ目の「アプリを公開する」ですが、実はさらなる問題が発生します。

今回 OAuth 同意画面/認証でユーザーに求めるアクセス権は、Google Drive でのファイルの追加(バックアップファイルのアップロード)、ファイルの一覧取得(ファイルの削除の時に使う)、ファイルの削除です。

これが「機密性の高いアクセス権」に属するので、アプリを公開するときに Google がアプリを検証(審査)するらしいのです。

OAuth App Verification Help Center - Google Cloud Platform Console ヘルプ

Apps that request access to scopes categorized as sensitive or restricted must complete Google's OAuth app verification before being granted access.

アプリの公開ステータスを「公開」にしようとしたときに下の画面が表示されます。

単にファイルをバックアップするプログラムとしてはこの検証は大変すぎます。そんな大層なプログラムではありません。

ということで、3つ目の「サービスアカウント認証にする」手段しかありません。

サービスアカウントでの認証の一手間

サービスアカウントでの認証をする場合、認証自体の手間は OAuth より楽です。OAuth 同意画面を作る必要もありません。

私は個人用の Google Drive にバックアップファイルをアップロードしたいので、それをするのが一手間ですが分かってしまえば簡単です。

こちらのサイトで詳しく説明されています。Google Drive の挙動について実験・検証されていてとても助かりました。

サービスアカウントを使って Google Drive(マイドライブ) に作成したファイルのオーナーはサービスアカウント

要するに、サービスアカウントでファイルをアップロードすると、そのサービスアカウントがファイルを所有することになります。

今回私はサーバーのファイルのバックアップを自分の個人用アカウントの Google Drive にアップロードしたいので、これはフォルダの共有(ドライブの共有)で解決できます。

個人用 Google Drive にサーバーのファイルをアップロードするフォルダを用意し、サービスアカウントのメールアドレスを共有者として追加します。

共有したそのフォルダの ID をアップロードの時に parents(親フォルダ)として指定すれば OK です。これで自分の Google Drive でファイルを確認できます。

$fileMetadata = new Drive\DriveFile(array(
  'name' => $fileName,
  'parents' => [$parentFolderId]
));
$content = file_get_contents($filePath);

// Upload
$file = $driveService->files->create($fileMetadata, array(
  'data' => $content,
  'uploadType' => 'multipart',
  'fields' => 'id',
));

ただし、注意点としては共有フォルダにアップロードしたファイルの所有者はサービスアカウントであること。

自分の Google Drive からサービスアカウントでアップロードしたこのファイルを削除しても、ファイルは削除されません。

共有された側がファイルを削除するとファイルは見えなくなりますが、サービスアカウントの Google Drive に残っています。

ファイルの所有者以外が削除しても本当に削除はできないということですね。上のサイトで検証されています。

もし自分の Google Drive でファイルを本当に削除したい場合、所有権を自分の Google アカウントに移譲する必要があります。これについても上のサイトで説明されています。

ただちょっと手間ですね。メールが届くというのも面倒。

あと、サービスアカウントが所有者であれば、個人用の Google Drive の容量が使われない(サービスアカウントの容量が使われる)という利点がありますので、私は今のところは所有権を移していません。

今のところサービスアカウント認証でうまく動いています。

2024-9-17 / 技術評論社
株式会社grasys (著), Google Cloud 大沼翔 (著), 西岡 典生 (著)
Nomeu / のめう
PCゲーマー

ゲームに没頭している時間は幸せ。

レビューとか良さげなセール情報とかを書いていきます。

コメントは X/Twitter にお願いします。