ζ3の覚書
カテゴリ: [開発日記]
⏱️ 8 MIN

GA4 API を自作スクリプトで叩く — OAuth Desktop Client + Refresh Token 永続化の実録

MCP が access_token しかくれなかったので、Refresh Token を自前で管理することにした話

※この記事は実際の作業メモを元に、AIが記述してます.信憑性は個人ブログ相当と思ってください.ごめんね


1. 背景

zeta3.net は Cloudflare Pages でホストしてる静的ブログで、GA4 でアクセス解析してる. んで、データ取る方法として、最初は Stape って MCP サーバーが提供する GA4 ツール使ってた.

ところが、Stape の GA4 MCP が発行する OAuth トークン、こんな制約があった:

項目
Access Tokenある(ya29. で始まる)
Refresh Tokenない
有効期限1時間(expires_in=3600
トークン保存場所~/.mcp-auth/ 以下

1時間ごとに再 OAuth が必要で、当然自動化できねーじゃんって話. Stape の有料プランに課金すれば refresh_token 対応してくれるらしいけど、そんなにお金かけることでもない.

「なら自分で OAuth Client 作って refresh_token 永続化すればいいじゃん」——ここが出発点.


2. 選択肢と意思決定

選択肢は2つ:

選択肢コストrefresh_token持続性
Stape 有料プラン課金たぶんもらえるベンダーロック
カスタム GCP OAuth Client無料確実にもらえる完全自前

後者一択.GCP の Desktop OAuth Client 作成して、OAuth 2.0 authorization code フローを Playwright で自動化、refresh_token 取得して .ga4-credentials/ に永続化する.


3. セットアップ手順

3.1 GCP OAuth Client の作成

  1. GCP Console → APIs & Services → Credentials
  2. OAuth consent screen: External で作成、必要な scope は analytics.readonly のみ
  3. Test user に自分の Google アカウント追加
  4. Credentials → Create Credentials → OAuth client ID → Desktop app 選択
  5. 発行された client_secret.json ダウンロード

ここでアプリケーションタイプ選びが重要.Web application を選ぶと redirect URI の登録が必要で、ローカルスクリプトから扱いにくい.Desktop app なら urn:ietf:wg:oauth:2.0:oob(out-of-band)って特殊な redirect URI が使えて、認証コードをブラウザに表示 → 手動コピーできる.

※OOB は将来的に非推奨になるらしいけど、2026年現在まだ使える.代わりに http://127.0.0.1:PORT の localhost redirect URI 使う手もある.

3.2 Playwright で OAuth フローを自動化

認証コードを手動コピーするのはだるいので、ブラウザ自動操作で authorization code 取得する.

const { chromium } = require('playwright');

const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();

const authUrl = 'https://accounts.google.com/o/oauth2/auth'
  + '?client_id=YOUR_CLIENT_ID'
  + '&redirect_uri=urn:ietf:wg:oauth:2.0:oob'
  + '&scope=https://www.googleapis.com/auth/analytics.readonly'
  + '&access_type=offline'
  + '&response_type=code';

await page.goto(authUrl);
// ここで手動ログイン or 既存セッション使う
const authCode = await page.locator('input[type="text"]').inputValue();

ポイント:

  • access_type=offline 指定で refresh_token が発行される(デフォだと access_token しか返らん)
  • headless: false でブラウザ表示、初回のみ手動ログイン必要

3.3 トークン交換

取得した authorization code を Google の token endpoint に POST する:

curl -s -X POST "https://oauth2.googleapis.com/token" \
  -d "code=$AUTH_CODE" \
  -d "client_id=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET" \
  -d "redirect_uri=urn:ietf:wg:oauth:2.0:oob" \
  -d "grant_type=authorization_code"

レスポンス:

{
  "access_token": "ya29.a0...",
  "expires_in": 3599,
  "refresh_token": "1//0gABCDEF...",
  "scope": "https://www.googleapis.com/auth/analytics.readonly",
  "token_type": "Bearer"
}

ここで初めて refresh_token が取れる.これを .ga4-credentials/tokens.json に保存する.

3.4 認証情報の永続化

tokens.json の構造:

{
  "client_id": "xxx.apps.googleusercontent.com",
  "client_secret": "GOCSPX-...",
  "refresh_token": "1//0gABCDEF...",
  "ga4_property_id": "properties/538003166"
}

client_secret.jsontokens.json.ga4-credentials/ に保存.このディレクトリは .gitignore に追加して誤公開防止.


4. 完成したスクリプト

ga4.sh — 50行、依存は curl + jq のみ:

#!/bin/bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CRED_DIR="$SCRIPT_DIR/.ga4-credentials"

CLIENT_ID=$(jq -r '.client_id' "$CRED_DIR/tokens.json")
CLIENT_SECRET=$(jq -r '.client_secret' "$CRED_DIR/tokens.json")
REFRESH_TOKEN=$(jq -r '.refresh_token' "$CRED_DIR/tokens.json")
PROPERTY=$(jq -r '.ga4_property_id' "$CRED_DIR/tokens.json")

TOKEN_RESPONSE=$(curl -s -X POST "https://oauth2.googleapis.com/token" \
  -d "client_id=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET" \
  -d "refresh_token=$REFRESH_TOKEN" \
  -d "grant_type=refresh_token")

ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token')

curl -s -X POST "https://analyticsdata.googleapis.com/v1beta/$PROPERTY:runRealtimeReport" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "dimensions":[{"name":"country"}],
    "metrics":[{"name":"activeUsers"},{"name":"eventCount"}],
    "minuteRanges":[{"startMinutesAgo":10}]
  }' | jq .

使い方:

./ga4.sh                    # レポート取得
./ga4.sh --access-token     # アクセストークンのみ

5. OAuth 2.0 Refresh Token フローの解説

やっていることは OAuth 2.0 の Refresh Token Grant そのもの.

sequenceDiagram
    participant Script as ga4.sh
    participant Google as Google OAuth2
    participant GA4 as GA4 Data API

    Script->>Google: POST /token (grant_type=refresh_token)
    Note over Script,Google: client_id + client_secret + refresh_token
    Google-->>Script: { access_token: "ya29...", expires_in: 3599 }

    Script->>GA4: GET /v1beta/properties/XXX:runRealtimeReport
    Note over Script,GA4: Authorization: Bearer ya29...
    GA4-->>Script: { rows: [...], totals: [...] }

重要ポイント:

  • refresh_token: 一度取得すれば Google が失効させない限り半永続的.access_type=offline 指定の authorization code フローでのみ発行
  • access_token: 1時間で死ぬ.refresh_token で都度取得
  • grant_type=refresh_token: 認可コードフローを通らずに新しい access_token を得られる
  • Desktop OAuth Client: ローカルスクリプト用途に最適.oob redirect URI が使える

6. ハマったポイント

gcloud ADC は使えなかった

gcloud auth application-default login \
  --client-id-file=.ga4-credentials/client_secret.json \
  --scopes=analytics.readonly

認証は通るんだけど、GA4 API の呼び出しで権限エラーになる. ADC(Application Default Credentials)は cloud-platform scope が必要で、これは過剰すぎる. 結果的に使えなかった.

refresh_token は初回のみ

authorization code フローで refresh_token が返ってくるのは初回の同意時のみ. 2回目以降の認可では返ってこないから、一度取ったら大事に保管する.

失効した場合の復旧手順:

  1. Google Account → セキュリティ → 「サードパーティアプリとサービス」
  2. 該当アプリを削除
  3. 再度 OAuth フローを通す

7. 得られた知見

  • MCP が access_token だけ渡してくるケースは意外とある.OAuth Client 自体はユーザーが GCP で管理してるのに、MCP サーバー側で意図的に refresh_token 捨ててる可能性が高い.トークン管理の複雑さを避けたいんだろうな.
  • 自前 OAuth Client + refresh_token 永続化、たかだか50行のシェルスクリプトで実現できる.ベンダー依存避けたいなら良い選択肢.
  • GCP OAuth Client のタイプ選びで後悔するパターンあるある.最初に Web application 選んで redirect URI でハマるぐらいなら Desktop App 一択.
  • Playwright によるブラウザ自動操作、OAuth みたいな人間介護必須フローの自動化に強力.初回ログインだけ人間が頑張ればあとは自動.

8. おまけ: リフレッシュトークンのテスト

refresh_token が本当に永続的かどうか、簡単に試せる:

# 1時間待つ必要なし — すぐに新しい access_token を何度でも取れる
for i in 1 2 3; do
  ./ga4.sh --access-token
  sleep 2
done

全部同じ refresh_token から新しい access_token が発行されるのを確認できる. GA4 API の rate limit には注意.


参考リンク