PythonプログラムでGoogle認証してGoogleのサービスを利用する

概要

プログラムの中でGoogleのサービス(API)を操作するとき、Google認証が必要になる。しかしこれがややこしく、Googleの公式のドキュメントの記述も古かったりサービスの種類によって方法がばらばらだったりして分かりにくい。この記事ではこれを整理して説明する。

具体的なケースを想定するとわかりやすいのだが、 たとえばGoogleアナリティクスからAPIでデータ取得し、そのデータをGoogle Cloud StorageやBigQueryに書き込みする場合、Googleアナリティクスのレポート閲覧権限(特定のビューに紐づいた)とGCPの権限(Google Cloud Storageの書き込み権限など)が必要になる。その権限を持ったGoogleアカウントで認証をすることになる。 この認証方法には大きく2通りの方法がある。

  1. サービスアカウント認証:必要な権限を付与されたアカウント(プログラム用の特殊なアカウント=サービスアカウント)を準備し、そのアカウントを使ってプログラムを実行する
  2. ユーザアカウント認証:一般のユーザ権限でプログラムを実行する

プログラムの挙動としてはどちらを選んでもいいが、使い道に依存する。 1の場合、たとえばGoogleアナリティクスでは特定のビューに対する権限をあらかじめサービスアカウントに付与しておいて、そのビューに限定したデータにアクセスする用途になる。 2の場合はプログラムを使う人(ユーザ)が実行時に自分の権限をプログラムに与えるイメージ。アクセスできるビューはユーザ次第である(使うユーザによって異なる)。

プログラム用の特殊なアカウント。Googleの画面にはログインできない。人間でない者(プログラムなど)が使うために設計されたアカウントである。 こんな形式のID

my-user-name@my-project.iam.gserviceaccount.com

GCPまたはGoogle Developerの管理画面で発行し、その後権限を付与する。

ユーザがパスワードを入力してログインするアカウント。GmailやGoogle Workspaceなどで使っているGoogleアカウントで、使うサービスの権限(特定のビューに対するGoogleアナリティクスの閲覧権限など)が付与されたもの。

バッチプログラムのようにプログラムがスタンドアロンで動くアプリはサービスアカウント、人間が使うアプリはユーザアカウントを想定することが多い。もちろんプログラムがスタンドアロンで使うケースであってもユーザアカウントを使うことはある。 利用するサービス側(Googleアナリティクスなど)で管理者が追加的に権限を付与できないケースもポリシーによってはあり、その場合はユーザ権限での認証を採用する。 たとえばGoogleアナリティクスの場合、アナリティクスの管理者が誰に対して権限を付与するかの違いになる。 サービスアカウントを使う場合、アナリティクスの管理者がサービスアカウントに権限を付与する。権限が不要になった時には管理者がサービスアカウントに付与した権限を削除することになる。管理者がプログラムによるアクセスを承認する手続きである。 それに対してユーザアカウント認証では、アナリティクスの管理者がプログラムの実行者に直接権限を付与することはない。管理者に権限を与えられたユーザがさらにプログラムに対して権限を付与する(サービスの利用を承認する)ことになる。管理者がプログラムのアクセスを承認するのではなく、ユーザが勝手に(自分の権限の範囲内で)プログラムを利用しているという位置づけである。 これはアナリティクス以外のGoogleサービスでも同様である。 サービスアカウントは管理者が権限設定で権限をはく奪しない限りそのサービスを使えなくなることはないが、ユーザアカウントによって認証されたものでは時間が経過すると使えなくなることがある。その場合は再びユーザが権限を付与する手続きをしなければ使えない。したがって永続的に動作するプログラムの場合にはサービスアカウントを使うのが望ましい。 重要なのはどちらでもGoogleのサービスを利用することは可能だということ。それらの性質を分かったうえで状況別に使いこなせるようになることである。

  1. 準備
  2. プログラムの実行
    1. 認証→scoped_credentialsの取得
    2. scoped_credentialsを使って個別サービスのAPIを呼び出す

準備と認証の方法がサービスアカウントとユーザアカウントによって異なる。その後の個別サービスを呼び出すところは共通。

以下の例はGoogleアナリティクスのレポートAPIからデータを取得し、BigQueryに格納するケースを想定する。つまりGoogleアナリティクス(読込のみ)とBigQueryを使う。

GCP/Google Developerのサービスアカウントの管理画面でサービスアカウントを発行し、認証キーを取得する。 サービスアカウントの発行画面は

GCPの管理画面「IAMとサービス」→「サービス アカウント」 Google Developer Consoleの画面「IAMと管理」→「サービス アカウント」

またはGCP/Google Developer Console共通で

「APIとサービス」→左メニューの「認証情報」→「認証情報作成」→「サービス アカウント」

(どちらでもいい) 認証キーはJSONで取得する。このJSONに認証で必要な情報がすべて含まれている(パスワードが別などということはない)。

権限付与はサービスによって大きく異なる。

  • GCP(BigQueryやストレージなど)→IAMの設定画面
  • その他Googleサービス(アナリティクス、ドライブなど)→各サービスのユーザ管理画面

→各サービスのメールアドレスとして上記のIDを指定する Google Search Consoleの場合

Googleアナリティクスの場合

google.oauth2パッケージを使う。google.oauth2google-authパッケージに含まれている。

pip install -U google-auth
python

キーをプログラムに実装する方法は

  • パスを指定して外部のサービスアカウントファイルを読み込む
  • キーの内容(JSONテキスト)をプログラム内に記述

がある。 サービスアカウントファイルを読み込む場合

from google.oauth2 import service_account
credentials = service_account.Credentials.from_service_account_file('service-account.json')
scoped_credentials = credentials.with_scopes(
  [
    'https://www.googleapis.com/auth/cloud-platform',
    'https://www.googleapis.com/auth/analytics.readonly'
  ])
python

service_account.Credentials.from_service_account_file()の引数で鍵ファイルのパスを指定する。credentials.with_scopes()では今回実際に使うGoogleサービスの範囲を指定する。今回はGoogleアナリティクスのレポートAPIからデータを取得し、BigQueryに入れるのでGoogleアナリティクスの読み込み権限とGCPの利用になる。 認証済みの資格情報scoped_credentialsが生成される。これを各サービスのAPIインスタンスを生成するときに指定する。 認証キーのJSONテキストを直接プログラムファイル内に記述する場合

from google.oauth2 import service_account
service_account_key = {
  "type": "service_account",
  "project_id": "my-project",
  "private_key_id": "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd",
  "private_key": "-----BEGIN PRIVATE KEY-----
MIIE...1X9dS
-----END PRIVATE KEY-----
",
  "client_email": "user1@my-project.iam.gserviceaccount.com",
  "client_id": "999999999999999999999",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/user1%40my-project.iam.gserviceaccount.com"
}
credentials = service_account.Credentials.from_service_account_info(service_account_key)
scoped_credentials = credentials.with_scopes(
  [
    'https://www.googleapis.com/auth/cloud-platform',
    'https://www.googleapis.com/auth/analytics.readonly'
  ])
python

service_account_keyの内容が認証キーファイルのJSONをそのまま貼り付けたものになる。コピー&ペーストでいい(そうするとPythonではdictとして読み込まれる)。 service_account.Credentials.from_service_account_info()の引数として上記のdictを指定する。あとは同じ(ここで生成されるcredentialsと先のファイル指定で生成するcredentialsは同じ)。

実行時は何も入力することはない

サービスアカウント編と同様にGoogleアナリティクスのレポートAPIからデータを取得し、BigQueryに格納するケースを想定する。つまりGoogleアナリティクス(読込のみ)とBigQueryを使う。 ユーザアカウントを使った認証はOAuth 2.0という方式をとる。手順は以下のようになる(方法1)

  1. アプリケーション自体の認証キー(クライアントIDとクライアントシークレット)を発行する
  2. 初回実行時にユーザが手動でアプリの承認を行う
  3. 承認済み認証ファイルをローカルに保存
  4. 次回以降、認証ファイルがあればそれを使う
  5. なければ改めて手動のアプリ承認フロー

サービスアカウントの時と同様に認証済みファイル(上記の3)をプログラム内に記載する方法もある。(方法2)

以下の手順になる。GCPまたはGoogle Developer Consoleの画面から「APIとサービス」でアクセスする。

  1. APIを有効化する
    「APIとサービス」→左メニューの「ライブラリ」→使うサービスを選択して「有効化」
  2. OAuthの同意画面を設定する
    「APIとサービス」→左メニューの「OAuth 同意画面」
  3. アプリケーションの認証キー(クライアントIDとクライアントシークレット)を発行する
    「APIとサービス」→左メニューの「認証情報」→「認証情報作成」→「OAuth 2.0 クライアント ID」

(1と2は同じプロジェクトですでに一度完了している場合は改めて不要)

クライアントIDとクライアントシークレットの2種類の文字列を取得する

こちらが一般的

google-auth-oauthlibパッケージを使う。

pip install -U google-auth-oauthlib
python
from google_auth_oauthlib.flow import InstalledAppFlow
import os

CLIENT_CONFIG = {'installed': {'client_id': '....apps.googleusercontent.com', 'client_secret': '...', 'redirect_uris': ['urn:ietf:wg:oauth:2.0:oob'], 'auth_uri': 'https://accounts.google.com/o/oauth2/auth', 'token_uri': 'https://oauth2.googleapis.com/token'}}
flow = InstalledAppFlow.from_client_config(CLIENT_CONFIG, ['https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/analytics.readonly'])
scoped_credentials = flow.run_local_server(port=0)
# または run_console() で認証コードをターミナルで入力
python

Googleの管理画面から取得したクライアントIDとクライアントシークレットをCLIENT_CONFIGclient_idclient_secretに指定する。 この認証方式ではプログラム実行時にブラウザが起動し、承認すると資格情報が取得される。run_console()を使うと認証コードをターミナルで入力する方式になる。

プログラムを実行すると、ユーザがアプリケーションを承認するためのAuth Codeを発行するためのURLが生成される

そこにブラウザでアクセスし、APIにアクセスするGoogleアカウントでログインして承認する。
Auth Codeが表示されるのでコピーし、プログラムの画面に貼り付ける。
認証が完了するのでプログラムが実行完了するまで待つ。

保存された資格情報ファイル(上の例ではカレントディレクトリのpydata_google_credentials.json)の内容をプログラム中にハードコーディングすることも可能。この方法は以下のとおり。

サービスアカウントのときと同じgoogle.oauth2パッケージを使う。google.oauth2google-authパッケージに含まれている。

pip install -U google-auth
python
from google.oauth2 import credentials
authorized_user_info = {"refresh_token": "1//aaaaa-bbbbbbb", "id_token": null, "token_uri": "https://accounts.google.com/o/oauth2/token", "client_id": "99999999999-999999aaaaaaaaaa9999aaaaaaaaaaaa.apps.googleusercontent.com", "client_secret": "aaaaaaaaaaaaaaaaaaaaaaaa", "scopes": ["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/analytics.readonly"]}
scoped_credentials = credentials.Credentials.from_authorized_user_info(
  authorized_user_info,
  [
    'https://www.googleapis.com/auth/cloud-platform',
    'https://www.googleapis.com/auth/analytics.readonly'
  ]
)
python

authorized_user_infoの内容が資格情報ファイルのJSONをそのまま貼り付けたものになる。コピー&ペーストでいい(そうするとPythonではdictとして読み込まれる)。 credentials.Credentials.from_authorized_user_info()の引数として上記のdictを指定する。

実行時は何も入力することはない

サービスによって大きく2パターンある。

主にGCP以外のGoogleのサービス(ドライブ、アナリティクスなど)のAPIを使う場合。例外的にGCPではCompute EngineやDataflowがこれに該当する。 https://github.com/googleapis/google-api-python-client

パッケージgoogleapiclientをインストールする

pip install -U google-api-python-client
python

Googleアナリティクスの場合

from googleapiclient.discovery import build

# サービスインスタンスの生成
# GA4の場合は google-analytics-data パッケージを使用。UA(ユニバーサルアナリティクス)は2024年7月に終了済み。
# analytics = build('analyticsreporting', 'v4', credentials=scoped_credentials)
# APIにリクエストを送る
response = analytics.reports().batchGet(body=request_body).execute()
python

サーチコンソールの場合

from googleapiclient.discovery import build

# サービスインスタンスの生成
# Search Console API(旧webmasters)
searchconsole = build('searchconsole', 'v1', credentials=scoped_credentials)
# APIにリクエストを送る
response = webmasters.searchanalytics().query(siteUrl='...', body=request_body).execute()
python

build()メソッドの引数credentialsで資格情報オブジェクトscoped_credentialsを指定する。

from googleapiclient.discovery import build
build('サービス名', 'vバージョン', credentials=scoped_credentials)
python

BigQueryやCloud Storageなど多くのGCPのサービスが該当する。 https://github.com/googleapis/google-cloud-python

GCPのサービスごとのパッケージgoogle-cloud-*****をインストールする

pip install -U google-cloud-bigquery
pip install -U google-cloud-storage
python

Cloud Storageの場合

from google.cloud import storage

# サービスインスタンスの生成
gcs_client = storage.Client(credentials=scoped_credentials, project='my-project')
# サービスを使う
bucket = gcs_client.get_bucket(CLOUD_STORAGE_BUCKET)
blob = bucket.blob('aaa.txt')
blob.upload_from_string('...', content_type='text/plain')
python

BigQueryの場合

from google.cloud import bigquery

# サービスインスタンスの生成
bigquery_client = bigquery.Client(credentials=scoped_credentials, project='my-project')
# サービスを使う
query_job = bigquery_client.query('INSERT ...')
query_job.result()
python

各サービスのClient()メソッドの引数credentialsで資格情報オブジェクトscoped_credentialsを指定する。 なおPandasでBigQueryにデータを入れる場合は

import pandas as pd
df = pd.DataFrame(rows)
df.to_gbq('mydataset.ga_result', project_id='my-project', credentials=scoped_credentials, chunksize=100000, if_exists='append')
python

PandasでBigQueryにデータを入れる場合はgoogle-cloud-bigqueryload_table_from_dataframe()を使うのが推奨。df.to_gbq()pandas-gbqに依存する。 以上を実装したサンプルは https://github.com/twofacauth/google-analytics-utils/blob/master/py/ga_to_bigquery.py にある。

上記は一つのアカウントに使用するすべてのGoogleサービスの権限を付与した。しかし実際にはGoogleサービスによって適用したい権限のアカウントが違う場合がある。たとえば

  • BigQueryはベンダ側で管理
  • Googleアナリティクスは事業会社側で管理

という状況で、お互いに相手の会社のアカウントに対して自社の権限を付与したくないというケースがある。 そうなるとBigQueryはベンダ側のサービスアカウントで、アナリティクスは事業会社のユーザ権限となる。それぞれのscoped_credentialsを使い分ければいい。

  • scoped_credentials1 -> ベンダのサービスアカウントから発行した資格情報
  • scoped_credentials2 -> 事業会社側のユーザアカウントから発行した資格情報
from google.oauth2 import service_account
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from google.cloud import bigquery

# ベンダ側の認証(BigQuery)
credentials = service_account.Credentials.from_service_account_file('vendor-service-account.json')
scoped_credentials1 = credentials.with_scopes(['https://www.googleapis.com/auth/cloud-platform'])

# 事業会社側の認証(アナリティクス)- google-auth-oauthlibでユーザ認証を取得
flow = InstalledAppFlow.from_client_config(CLIENT_CONFIG, ['https://www.googleapis.com/auth/analytics.readonly'])
scoped_credentials2 = flow.run_local_server(port=0)  # または run_console()

# アナリティクスのAPIにアクセスする(事業会社側の資格情報を使用)
analytics = build('analyticsreporting', 'v4', credentials=scoped_credentials2)
response = analytics.reports().batchGet(body=request_body).execute()
  :

# BigQueryを呼び出す(ベンダ側の資格情報を使用)
bigquery_client = bigquery.Client(credentials=scoped_credentials1, project='my-project')
# テーブルへの行挿入例
errors = bigquery_client.insert_rows_json('mydataset.ga', rows)
python

アナリティクスとBigQueryの引数credentialsで指定する資格情報を使い分けている。GCEやCloud Run上ではApplication Default Credentials(google.auth.default())を使うと、サービスアカウントの鍵ファイルを明示的に指定せずに認証できる。