google について
googleは、Google OAuth 2.0、OpenID Connect、Gmail、Calendar、DriveのAPIをローカルでエミュレートするツールです。実際のGoogleサービスに接続せず、シードデータを使って認証やAPI連携をテストする開発者向けです。
主な機能
- 01OAuth 2.0とOIDCフローのエミュレーション
- 02PKCE、トークン更新、取り消しに対応
- 03Gmail、Calendar、DriveのAPIを提供
- 04ユーザーとサービスデータのシード
- 05スコープ検証の設定
活用シーン
- 01OAuthサインインとトークン処理のテスト
- 02Gmail、Calendar、Drive APIクライアントの開発
- 03スコープに応じたアクセス制御や認証エラーの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vercel-labs/ emulate --skill googleインストール後、Claude Code で/googleを入力して読み込まれたか確認します。
詳細
Google OAuth 2.0 / OIDC + Gmail、Calendar、Drive エミュレーター
認可コードフロー、PKCE サポート、ID トークン、OIDC ディスカバリー、リフレッシュトークンに加え、Gmail、Google Calendar、Google Drive の REST API サーフェスをエミュレートします。
起動
ローカルのリスナーは、デフォルトで 127.0.0.1 にバインドします。コンテナーやほかのマシンからアクセスするには、CLI に --host 0.0.0.0 を渡すか、createEmulator に hostname: '0.0.0.0' を渡してください。クライアントからアクセスできる URL を公開するには、--base-url または baseUrl を設定してください。
# Google only
npx emulate --service google
# Default port
# http://localhost:4002
または、プログラムから起動します。
import { createEmulator } from 'emulate'
const google = await createEmulator({ service: 'google', port: 4002 })
// google.url === 'http://localhost:4002'
アプリの接続先をエミュレーターに設定する
環境変数
GOOGLE_EMULATOR_URL=http://localhost:4002
OAuth URL のマッピング
| 実際の Google URL | エミュレーター URL |
|---|---|
https://accounts.google.com/o/oauth2/v2/auth | $GOOGLE_EMULATOR_URL/o/oauth2/v2/auth |
https://oauth2.googleapis.com/token | $GOOGLE_EMULATOR_URL/oauth2/token |
https://www.googleapis.com/oauth2/v2/userinfo | $GOOGLE_EMULATOR_URL/oauth2/v2/userinfo |
https://accounts.google.com/.well-known/openid-configuration | $GOOGLE_EMULATOR_URL/.well-known/openid-configuration |
https://www.googleapis.com/oauth2/v3/certs | $GOOGLE_EMULATOR_URL/oauth2/v3/certs |
https://gmail.googleapis.com/gmail/v1/... | $GOOGLE_EMULATOR_URL/gmail/v1/... |
https://www.googleapis.com/calendar/v3/... | $GOOGLE_EMULATOR_URL/calendar/v3/... |
https://www.googleapis.com/drive/v3/... | $GOOGLE_EMULATOR_URL/drive/v3/... |
google-auth-library(Node.js)
import { OAuth2Client } from 'google-auth-library'
const GOOGLE_URL = process.env.GOOGLE_EMULATOR_URL ?? 'https://accounts.google.com'
const client = new OAuth2Client({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
redirectUri: 'http://localhost:3000/api/auth/callback/google',
})
const emulatorAuthorizeUrl = `${GOOGLE_URL}/o/oauth2/v2/auth?client_id=${process.env.GOOGLE_CLIENT_ID}&redirect_uri=...&scope=openid+email+profile&response_type=code&state=...`
Auth.js / NextAuth.js
import Google from '@auth/core/providers/google'
Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorization: {
url: `${process.env.GOOGLE_EMULATOR_URL}/o/oauth2/v2/auth`,
params: { scope: 'openid email profile' },
},
token: {
url: `${process.env.GOOGLE_EMULATOR_URL}/oauth2/token`,
},
userinfo: {
url: `${process.env.GOOGLE_EMULATOR_URL}/oauth2/v2/userinfo`,
},
})
Passport.js
import { Strategy as GoogleStrategy } from 'passport-google-oauth20'
const GOOGLE_URL = process.env.GOOGLE_EMULATOR_URL ?? 'https://accounts.google.com'
new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: 'http://localhost:3000/api/auth/callback/google',
authorizationURL: `${GOOGLE_URL}/o/oauth2/v2/auth`,
tokenURL: `${GOOGLE_URL}/oauth2/token`,
userProfileURL: `${GOOGLE_URL}/oauth2/v2/userinfo`,
}, verifyCallback)
シード設定
google:
users:
- email: [email protected]
name: Test User
given_name: Test
family_name: User
picture: https://lh3.googleusercontent.com/a/default-user
email_verified: true
locale: en
- email: [email protected]
name: Developer
- email: [email protected]
name: Admin
hd: acme.com
oauth_clients:
- client_id: my-client-id.apps.googleusercontent.com
client_secret: GOCSPX-secret
name: My App
redirect_uris:
- http://localhost:3000/api/auth/callback/google
labels:
- id: Label_ops
user_email: [email protected]
name: Ops/Review
color_background: "#DDEEFF"
color_text: "#111111"
messages:
- id: msg_welcome
user_email: [email protected]
thread_id: thr_welcome
from: "[email protected]"
to: [email protected]
subject: Welcome to the Gmail emulator
body_text: You can now test Gmail flows locally.
label_ids: [INBOX, UNREAD, CATEGORY_UPDATES]
date: "2025-01-04T10:00:00.000Z"
calendars:
- id: primary
user_email: [email protected]
summary: [email protected]
primary: true
selected: true
time_zone: UTC
calendar_events:
- id: evt_kickoff
user_email: [email protected]
calendar_id: primary
summary: Project Kickoff
start_date_time: "2025-01-10T09:00:00.000Z"
end_date_time: "2025-01-10T09:30:00.000Z"
attendees:
- email: [email protected]
display_name: Test User
conference_entry_points:
- entry_point_type: video
uri: https://meet.google.com/example
label: Google Meet
hangout_link: https://meet.google.com/example
drive_items:
- id: drv_docs
user_email: [email protected]
name: Docs
mime_type: application/vnd.google-apps.folder
parent_ids: [root]
- id: drv_readme
user_email: [email protected]
name: README.md
mime_type: text/markdown
parent_ids: [drv_docs]
data: "# Hello World"
OAuth クライアントが設定されていない場合、エミュレーターは任意の client_id を受け入れます。クライアントが設定されている場合は、client_id、client_secret、redirect_uri が厳密に検証されます。
ローカルのベアラートークンを使うワークフローに対応するため、Google 認証はデフォルトで制限が緩やかです。Gmail、Calendar、Drive、userinfo の各ルートで、エミュレーターが発行した有効期限内のアクセストークンを必須にするには、シード設定で google.strict_scopes: true を設定します。認証情報が無効な場合は 401 UNAUTHENTICATED が返されます。必要なメソッドスコープがない場合は、insufficientPermissions を含む 403 が返されます。厳密モードでは、読み取り、書き込み、送信、設定、メタデータの各スコープを区別し、userinfo には許可された ID クレームのみが含まれます。アクセストークンまたはリフレッシュトークンを取り消すと、関連付けられたアクセストークンとリフレッシュトークンも無効になります。ディスカバリー、署名鍵、サインインページは引き続き公開されます。このモードではメソッドスコープを確認しますが、drive.file や Gmail アドオンのスコープなど、リソース固有の許可はモデル化されていません。
ホスト対象ドメイン(hd)クレーム
Google Workspace アカウントでは、ユーザーのホスト対象ドメインを示す hd クレームが ID トークンと userinfo レスポンスに含まれます。エミュレーターはユーザーのメールアドレスのドメインから、この値を自動的に導出します。一般向けドメイン(gmail.com、googlemail.com)では、実際の Google の動作と同様に、このクレームは省略されます。
導出された値を上書きするには、シードしたユーザーに hd を設定します。クレームを完全に省略するには、hd を空文字列に設定します。
OAuth / OIDC エンドポイント
OIDC ディスカバリー
curl http://localhost:4002/.well-known/openid-configuration
JWKS
curl http://localhost:4002/oauth2/v3/certs
エミュレーターが発行した ID トークンの検証に使用する RSA 公開鍵を返します。Google ID トークンは RS256 を使用し、ディスカバリー文書でも同じアルゴリズムが公開されます。
認可
# Browser flow: redirects to a user picker page
curl -v "http://localhost:4002/o/oauth2/v2/auth?\
client_id=my-client-id.apps.googleusercontent.com&\
redirect_uri=http://localhost:3000/api/auth/callback/google&\
scope=openid+email+profile&\
response_type=code&\
state=random-state&\
nonce=random-nonce"
PKCE 用の code_challenge と code_challenge_method をサポートしています。
トークン交換
curl -X POST http://localhost:4002/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "code=<authorization_code>&\
client_id=my-client-id.apps.googleusercontent.com&\
client_secret=GOCSPX-secret&\
redirect_uri=http://localhost:3000/api/auth/callback/google&\
grant_type=authorization_code"
application/json のボディも受け付けます。次を返します。
{
"access_token": "google_...",
"refresh_token": "google_refresh_...",
"id_token": "<jwt>",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid email profile"
}
リフレッシュトークン
curl -X POST http://localhost:4002/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "refresh_token=google_refresh_...&\
client_id=my-client-id.apps.googleusercontent.com&\
client_secret=GOCSPX-secret&\
grant_type=refresh_token"
新しい access_token を返します(更新時に新しい refresh_token や id_token は発行されません)。
ユーザー情報
curl http://localhost:4002/oauth2/v2/userinfo \
-H "Authorization: Bearer google_..."
トークンの取り消し
curl -X POST http://localhost:4002/oauth2/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=google_..."
Gmail API
すべての Gmail エンドポイントは /gmail/v1/users/:userId/... 配下にあります。:userId は me または認証済みユーザーのメールアドレスです。
メッセージ
# List messages (filter by labels, search query)
curl "http://localhost:4002/gmail/v1/users/me/messages?labelIds=INBOX&q=from:welcome&maxResults=10" \
-H "Authorization: Bearer $TOKEN"
# Get message (format: full, metadata, minimal, raw)
curl "http://localhost:4002/gmail/v1/users/me/messages/msg_welcome?format=full" \
-H "Authorization: Bearer $TOKEN"
# Send message
curl -X POST http://localhost:4002/gmail/v1/users/me/messages/send \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"to": "[email protected]", "subject": "Hello", "body_text": "Hi there"}'
# Insert message (bypass send)
curl -X POST http://localhost:4002/gmail/v1/users/me/messages \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"to": "[email protected]", "from": "[email protected]", "subject": "Test", "body_text": "Body", "labelIds": ["INBOX"]}'
# Import message
curl -X POST http://localhost:4002/gmail/v1/users/me/messages/import \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"to": "[email protected]", "from": "[email protected]", "subject": "Imported", "body_text": "Content"}'
# Modify labels on a message
curl -X POST http://localhost:4002/gmail/v1/users/me/messages/msg_welcome/modify \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"addLabelIds": ["STARRED"], "removeLabelIds": ["UNREAD"]}'
# Trash / untrash
curl -X POST http://localhost:4002/gmail/v1/users/me/messages/msg_welcome/trash \
-H "Authorization: Bearer $TOKEN"
curl -X POST http://localhost:4002/gmail/v1/users/me/messages/msg_welcome/untrash \
-H "Authorization: Bearer $TOKEN"
# Delete permanently
curl -X DELETE http://localhost:4002/gmail/v1/users/me/messages/msg_welcome \
-H "Authorization: Bearer $TOKEN"
# Batch modify
curl -X POST http://localhost:4002/gmail/v1/users/me/messages/batchModify \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"ids": ["msg_welcome", "msg_build"], "addLabelIds": ["STARRED"]}'
# Batch delete
curl -X POST http://localhost:4002/gmail/v1/users/me/messages/batchDelete \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"ids": ["msg_welcome"]}'
# Get attachment
curl http://localhost:4002/gmail/v1/users/me/messages/msg_id/attachments/att_id \
-H "Authorization: Bearer $TOKEN"
アップロード用のバリエーションも /upload/gmail/v1/users/:userId/messages、.../messages/send、.../messages/import で利用できます。
下書き
# List drafts
curl http://localhost:4002/gmail/v1/users/me/drafts \
-H "Authorization: Bearer $TOKEN"
# Create draft
curl -X POST http://localhost:4002/gmail/v1/users/me/drafts \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"message": {"to": "[email protected]", "subject": "Draft subject", "body_text": "Draft body"}}'
# Get draft (format: full, metadata, minimal, raw)
curl "http://localhost:4002/gmail/v1/users/me/drafts/draft_id?format=full" \
-H "Authorization: Bearer $TOKEN"
# Update draft
curl -X PUT http://localhost:4002/gmail/v1/users/me/drafts/draft_id \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"message": {"subject": "Updated subject", "body_text": "Updated body"}}'
# Send draft
curl -X POST http://localhost:4002/gmail/v1/users/me/drafts/send \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"id": "draft_id"}'
# Delete draft
curl -X DELETE http://localhost:4002/gmail/v1/users/me/drafts/draft_id \
-H "Authorization: Bearer $TOKEN"
スレッド
# List threads (filter by labels, search query)
curl "http://localhost:4002/gmail/v1/users/me/threads?labelIds=INBOX&maxResults=20" \
-H "Authorization: Bearer $TOKEN"
# Get thread (all messages in thread)
curl "http://localhost:4002/gmail/v1/users/me/threads/thr_welcome?format=full" \
-H "Authorization: Bearer $TOKEN"
# Modify labels on all messages in thread
curl -X POST http://localhost:4002/gmail/v1/users/me/threads/thr_welcome/modify \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"addLabelIds": ["STARRED"], "removeLabelIds": ["UNREAD"]}'
# Trash / untrash / delete thread
curl -X POST http://localhost:4002/gmail/v1/users/me/threads/thr_welcome/trash \
-H "Authorization: Bearer $TOKEN"
curl -X DELETE http://localhost:4002/gmail/v1/users/me/threads/thr_welcome \
-H "Authorization: Bearer $TOKEN"
ラベル
# List labels
curl http://localhost:4002/gmail/v1/users/me/labels \
-H "Authorization: Bearer $TOKEN"
# Get label
curl http://localhost:4002/gmail/v1/users/me/labels/INBOX \
-H "Authorization: Bearer $TOKEN"
# Create label
curl -X POST http://localhost:4002/gmail/v1/users/me/labels \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "My Label", "color": {"backgroundColor": "#DDEEFF", "textColor": "#111111"}}'
# Update label (PUT replaces, PATCH merges)
curl -X PATCH http://localhost:4002/gmail/v1/users/me/labels/Label_ops \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Ops/Reviewed"}'
# Delete label (user labels only)
curl -X DELETE http://localhost:4002/gmail/v1/users/me/labels/Label_ops \
-H "Authorization: Bearer $TOKEN"
履歴とウォッチ
# List history changes since a given historyId
curl "http://localhost:4002/gmail/v1/users/me/history?startHistoryId=1&historyTypes=messageAdded&maxResults=100" \
-H "Authorization: Bearer $TOKEN"
# Set up push notification watch (stub)
curl -X POST http://localhost:4002/gmail/v1/users/me/watch \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"topicName": "projects/my-project/topics/gmail", "labelIds": ["INBOX"]}'
# Stop watch
curl -X POST http://localhost:4002/gmail/v1/users/me/stop \
-H "Authorization: Bearer $TOKEN"
設定
# List filters
curl http://localhost:4002/gmail/v1/users/me/settings/filters \
-H "Authorization: Bearer $TOKEN"
# Create filter (auto-label incoming messages matching criteria)
curl -X POST http://localhost:4002/gmail/v1/users/me/settings/filters \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"criteria": {"from": "[email protected]"}, "action": {"addLabelIds": ["Label_ops"]}}'
# Delete filter
curl -X DELETE http://localhost:4002/gmail/v1/users/me/settings/filters/filter_id \
-H "Authorization: Bearer $TOKEN"
# List forwarding addresses
curl http://localhost:4002/gmail/v1/users/me/settings/forwardingAddresses \
-H "Authorization: Bearer $TOKEN"
# List send-as aliases
curl http://localhost:4002/gmail/v1/users/me/settings/sendAs \
-H "Authorization: Bearer $TOKEN"
Google Calendar API
検出
Calendar の Discovery ドキュメントは公開されており、エミュレーターがサポートする Calendar v3 のメソッドについて説明しています。
curl http://localhost:4002/discovery/v1/apis/calendar/v3/rest
カレンダーリスト
curl http://localhost:4002/calendar/v3/users/me/calendarList \
-H "Authorization: Bearer $TOKEN"
イベント
# List events (filter by time range, search, order)
curl "http://localhost:4002/calendar/v3/calendars/primary/events?\
timeMin=2025-01-01T00:00:00Z&timeMax=2025-12-31T23:59:59Z&maxResults=50&orderBy=startTime" \
-H "Authorization: Bearer $TOKEN"
# Create event
curl -X POST http://localhost:4002/calendar/v3/calendars/primary/events \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"summary": "Team Meeting", "start": {"dateTime": "2025-01-10T14:00:00Z"}, "end": {"dateTime": "2025-01-10T15:00:00Z"}, "attendees": [{"email": "[email protected]"}]}'
# Delete event
curl -X DELETE http://localhost:4002/calendar/v3/calendars/primary/events/evt_kickoff \
-H "Authorization: Bearer $TOKEN"
FreeBusy
curl -X POST http://localhost:4002/calendar/v3/freeBusy \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"timeMin": "2025-01-10T00:00:00Z", "timeMax": "2025-01-10T23:59:59Z", "items": [{"id": "primary"}]}'
Google Drive API
ファイル
# List files (with query filter, pagination, ordering)
curl "http://localhost:4002/drive/v3/files?q='root'+in+parents&pageSize=20" \
-H "Authorization: Bearer $TOKEN"
# Create file (JSON metadata)
curl -X POST http://localhost:4002/drive/v3/files \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "notes.txt", "mimeType": "text/plain", "parents": ["root"]}'
# Create file with content (multipart/related upload)
curl -X POST http://localhost:4002/upload/drive/v3/files \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: multipart/related; boundary=boundary" \
--data-binary $'--boundary\r\nContent-Type: application/json\r\n\r\n{"name":"data.csv","mimeType":"text/csv"}\r\n--boundary\r\nContent-Type: text/csv\r\n\r\na,b,c\n1,2,3\r\n--boundary--'
# Get file metadata
curl http://localhost:4002/drive/v3/files/drv_readme \
-H "Authorization: Bearer $TOKEN"
# Download file content
curl "http://localhost:4002/drive/v3/files/drv_readme?alt=media" \
-H "Authorization: Bearer $TOKEN"
# Update file (PATCH or PUT; move parents with query params)
curl -X PATCH "http://localhost:4002/drive/v3/files/drv_readme?addParents=folder_id&removeParents=root" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "README-updated.md"}'
共通パターン
完全な認可コードフロー
GOOGLE_URL="http://localhost:4002"
CLIENT_ID="my-client-id.apps.googleusercontent.com"
CLIENT_SECRET="GOCSPX-secret"
REDIRECT_URI="http://localhost:3000/api/auth/callback/google"
# 1. Open in browser (user picks a seeded account)
# $GOOGLE_URL/o/oauth2/v2/auth?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&scope=openid+email+profile&response_type=code&state=abc
# 2. After user selection, emulator redirects to:
# $REDIRECT_URI?code=<code>&state=abc
# 3. Exchange code for tokens
curl -X POST $GOOGLE_URL/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "code=<code>&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&redirect_uri=$REDIRECT_URI&grant_type=authorization_code"
# 4. Fetch user info with the access_token
curl $GOOGLE_URL/oauth2/v2/userinfo \
-H "Authorization: Bearer <access_token>"
OIDC Discovery ベースのセットアップ
import { Issuer } from 'openid-client'
const googleIssuer = await Issuer.discover(
process.env.GOOGLE_EMULATOR_URL ?? 'https://accounts.google.com'
)
const client = new googleIssuer.Client({
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
redirect_uris: ['http://localhost:3000/api/auth/callback/google'],
})
Gmail メッセージを送信してスレッドを確認する
TOKEN="test_token_admin"
BASE="http://localhost:4002"
# Send a message
curl -X POST $BASE/gmail/v1/users/me/messages/send \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"to": "[email protected]", "subject": "Test", "body_text": "Hello"}'
# List threads in INBOX
curl "$BASE/gmail/v1/users/me/threads?labelIds=INBOX" \
-H "Authorization: Bearer $TOKEN"
組み込みエミュレーターと併用するカスタムエミュレーター
npx emulate init --custom inventory を使ってサードパーティ API エミュレーターのひな形を作成し、テストします。emulate の defineConfig を使って、emulate.config.ts に { emulator: "google" } などの組み込みエントリーと並べて登録します。npx emulate start --watch を実行すると、インポートが再読み込みされ、表示された /_emulate URL でカスタム状態を確認できます。再読み込みが成功すると、実行状態はシードにリセットされます。既存のフラットなシード設定も引き続き利用できます。--config で明示的なファイルを指定します。サードパーティ API エミュレーターの作成とテストについては、https://emulate.dev/docs/custom-emulators を参照してください。