💎

gcloud コマンドで認証してGoogle Drive APIを使うときの罠

に公開

はじめに:何をしようとしたか

ローカル環境でシェルスクリプトを組み、Google Driveの特定フォルダにある画像を順番に文字起こしする、というシンプルなCLIツールを作ろうとしたのが始まりでした。

当初の計画:

  1. gcloud CLIで認証する。
  2. gcloud auth application-default print-access-tokenで一時的なアクセストークンを取得する。
  3. そのトークンを使ってcurlでGoogle Drive APIを叩き、ファイルリストを取得したり、ファイルをダウンロードしたりする。
  4. ダウンロードした画像をGemini CLIに渡して文字起こしする。

一見、ローカル環境からGoogleのサービスを利用するごく標準的なアプローチのはずでした。

第1の壁:権限が足りない (Insufficient Scopes)

まず最初にcurlでAPIを叩くと、あっけなく403 PERMISSION_DENIEDエラーが返ってきました。

エラーメッセージ:
"Request had insufficient authentication scopes."

原因と解決策

これは「認証はできているが、許可されている操作の範囲(スコープ)にGoogle Driveの操作が含まれていない」という状態です。

gcloud auth application-default loginをただ実行しただけでは、Google Cloud Platform (GCP) サービス用の基本的なスコープしか付与されません。Google Drive APIを操作するには、専用のスコープを明示的に要求する必要がありました。

解決策:
--scopesフラグを使って、GCPの基本スコープと、Google Driveの読み取りスコープの両方をカンマ区切りで指定して再度ログインする。

gcloud auth application-default login \
--scopes=https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/drive.readonly

📝 なぜ両方必要なのか?
--scopesフラグはデフォルトのスコープに追加するのではなく、上書きします。そのため、drive.readonlyだけを指定すると、今度はgcloud自体が機能するために必要なcloud-platformスコープが消えてしまい、別のエラーが発生します。


第2の壁:どのプロジェクトの予算? (Requires a Quota Project)

スコープ問題を解決し、再度APIを叩くと、またしても403 PERMISSION_DENIED。しかし、エラーメッセージは変わっていました。

エラーメッセージ:
"The drive.googleapis.com API requires a quota project, which is not set by default."

原因と解決策

これは「APIの利用料金や使用量を、どのGCPプロジェクトの予算として計上すればいいか分からない」というエラーです。個人のPCからユーザー認証でAPIを使う場合、この「クォータプロジェクト」は自分で設定してあげる必要がありました。

解決策:

  1. APIを有効化: Google Cloudコンソールで、対象のプロジェクトに対して「Google Drive API」を有効にする。
  2. クォータプロジェクトを設定: gcloudで、どのプロジェクトに紐付けるかを設定する。

# YOUR_PROJECT_IDを実際のプロジェクトIDに置き換える
gcloud auth application-default set-quota-project YOUR_PROJECT_ID

最後の壁:設定したはずなのに… (Consumerプロジェクトの謎)

認証情報をリフレッシュし、設定ファイル(application_default_credentials.json)にquota_project_idが正しく書き込まれていることを確認。APIも有効になっている。これでOKのはず..なのに、何度やっても同じ「Quota Projectが必要」エラーが返ってくるのです。

万策尽きたかと思われましたが、エラーメッセージを注意深く見るとヒントが隠されていました。

エラーメッセージ内のdetails:

"metadata": {
  "service": "drive.googleapis.com",
  "consumer": "projects/764086051850" // ← 謎のプロジェクト番号
}

APIサーバーがこのリクエストを処理しようとしたConsumerのプロジェクト番号が、私が設定したプロジェクト番号と違っていたのです。

なぜこの現象が起きるのか

これは、gcloudがデフォルトで使う**「認証用の鍵(OAuth 2.0 クライアントID)」の所有者**に原因があります。

私たちがgcloud auth loginを実行する際、その認証プロセス自体が、Googleの管理する特殊なプロジェクト(この例では...764086051850)に所属する「gcloudツール用の共通の鍵」を使って行われます。

通常、API側は利用者が設定した「クォータプロジェクト」を尊重してくれます。しかし、Google Drive APIのような一部のAPIでは、この認証フローが厳格で、クォータプロジェクト設定を無視して、認証に使われた「鍵」の所有者であるgcloudのデフォルトプロジェクトを請求先と見なしてしまうのです。当然、そのプロジェクトではAPIが有効になっていないため、エラーが返され続けます。

最終的な解決策

根本的な解決策は、gcloudの「共通の鍵」を使うのをやめ、自分自身のプロジェクトに所属する、自分専用の「鍵(OAuth 2.0 クライアントID)」を作成し、それを使って認証することです。

  1. OAuth同意画面を設定する:
    Cloudコンソールで、自分のプロジェクト用に認証画面の基本情報(アプリ名など)を設定します。これは、あなた専用の鍵を発行するための前提条件です。

  2. OAuth 2.0 クライアントIDを作成する:
    同じくCloudコンソールで、「アプリケーションの種類」を「デスクトップアプリ」として、新しいクライアントIDを作成します。作成後、クライアントIDとシークレットが書かれたJSONファイルをダウンロードします。

  3. ダウンロードした自分専用の鍵で、再度認証する:
    gcloud authコマンドに、ダウンロードしたJSONファイルを指定するオプションを追加して実行します。

    # /path/to/your/client_secret.json の部分を、ダウンロードしたファイルの実際のパスに置き換える
    gcloud auth application-default login \
      --client-id-file=/path/to/your/client_secret.json \
      --scopes=https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/drive.readonly
    

この手順で認証を行うと、認証プロセス全体があなたのプロジェクトに紐付きます。APIサーバー側から見ても、リクエストのConsumerはあなたのプロジェクトだと正しく認識され、認証が通るようになります。

まとめ

やりたいことは単純なはずなのに、小一時間溶かしました。

参考

Discussion