Contents
この記事の構成と前提条件
この記事の構成と前提を最初に示します。
前提を満たすことで短時間検証の目安(約1時間)が現実的になります。
この記事の構成
この記事は以下の章で構成しています。
- 認証方式の比較と設計方針
- Authorization Code フロー(PKCE 含む)の実装例
- Client Credentials フローの実装例
- エンドポイント・スコープ・検証手順
- 運用(シークレット管理・ローテーション)と法務チェックリスト
認証方式とフロー選択(Togetter API)
Togetter API では一般的に OAuth2 ベースの認証を用います。
ユースケースに応じて Authorization Code(ユーザー代理)と Client Credentials(サーバ間)を使い分けます。
ここで違いを整理し、実務で確認すべき設計ポイントを示します。
フローの比較(Authorization Code vs Client Credentials)
下表は代表的な違いの例示です。実際の挙動は提供元の仕様に従ってください。
| 項目 | Authorization Code(例示) | Client Credentials(例示) |
|---|---|---|
| 想定用途 | ユーザー代理で非公開データにアクセス | サーバ間で公開データを取得 |
| ユーザー同意 | 必要 | 不要 |
| refresh_token | 通常発行されることが多い(要確認) | 通常発行されない |
| クライアント種別 | 公開クライアント(SPA)や機密クライアント | 機密クライアント(サーバ) |
| セキュリティ | PKCE 推奨(SPA/モバイル) | client_secret 管理が必須 |
使い分け時の実務観点
Authorization Code を使う場面ではユーザー同意やリダイレクトURI管理が重要です。
Client Credentials はトークン有効期間とローテーション設計が運用上の鍵になります。
Authorization Code フロー:実践実装とPKCE(Togetter API)
Authorization Code フローはユーザー代理アクセスで用います。
SPA やモバイルでは PKCE を組み合わせて安全性を高めてください。
以下はブラウザ側の認可URL生成からサーバ側の code→token 交換までの具体例です。
認可URL生成(ブラウザ側)
認可URLには response_type, client_id, redirect_uri, scope, state, code_challenge, code_challenge_method を付けます。
state は CSRF 対策として必ず生成し検証してください。
例(構成イメージ):
|
1 2 3 4 5 6 7 8 9 |
https://auth.example.com/authorize? response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=https%3A%2F%2Fyour.app%2Foauth%2Fcallback &scope=summaries.read%20users.read &state=RANDOM_STATE &code_challenge=CODE_CHALLENGE &code_challenge_method=S256 |
PKCE の生成例
code_verifier と code_challenge はクライアント側で生成します。以下は Node.js と Python の例です。
Node.js(互換的な実装)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const crypto = require('crypto'); function base64url(buf) { return buf.toString('base64') .replace(/=/g, '') .replace(/\+/g, '-') .replace(/\//g, '_'); } const code_verifier = base64url(crypto.randomBytes(32)); const code_challenge = base64url(crypto.createHash('sha256').update(code_verifier).digest()); // 保存: code_verifier をセッション等で保持しておく console.log('code_verifier:', '<REDACTED>'); console.log('code_challenge:', code_challenge); |
Python(例)
|
1 2 3 4 5 6 7 8 9 |
import secrets, hashlib, base64 code_verifier = secrets.token_urlsafe(64) code_challenge = base64.urlsafe_b64encode( hashlib.sha256(code_verifier.encode()).digest() ).rstrip(b'=').decode('ascii') # code_verifier はセッション等で保持 |
code_verifier はリダイレクト後の token 交換時に必要です。code_verifier の値はログに出さないでください。
リダイレクトで code を受け取る(サーバ側)
認可後、登録した redirect_uri に code と state が付与されます。
サーバ側で state を検証し、code を受け取って token 交換処理へ進めます。
Express の簡易例(受け取り)
|
1 2 3 4 5 6 |
app.get('/oauth/callback', async (req, res) => { const { code, state } = req.query; // state を検証する // code を使ってトークン交換へ(下記参照) }); |
code を token に交換する(curl / Python / Node.js)
この段階で code と code_verifier をトークンエンドポイントへ送ります。以下は PKCE を使う例です。
curl(公開クライアント / PKCE)
|
1 2 3 4 |
curl -X POST "$TOKEN_URL" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code&code=${CODE}&redirect_uri=${REDIRECT_URI}&client_id=${CLIENT_ID}&code_verifier=${CODE_VERIFIER}" |
curl(機密クライアント:Basic 認証)
|
1 2 3 4 5 |
curl -u "${CLIENT_ID}:${CLIENT_SECRET}" \ -X POST "$TOKEN_URL" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code&code=${CODE}&redirect_uri=${REDIRECT_URI}" |
Python(requests、例外処理あり)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import os, requests, logging logger = logging.getLogger(__name__) try: resp = requests.post( os.environ['TOGETTER_TOKEN_URL'], data={ 'grant_type': 'authorization_code', 'code': code, 'redirect_uri': os.environ['TOGETTER_REDIRECT_URI'], 'client_id': os.environ['TOGETTER_CLIENT_ID'], 'code_verifier': code_verifier }, timeout=10 ) resp.raise_for_status() token = resp.json() expires_in = token.get('expires_in') logger.info('token obtained, expires_in=%s', expires_in) # トークン本体はログに出さない except requests.RequestException as e: logger.error('token exchange failed: %s', str(e)) raise |
Node.js(axios、PKCE を使う例、URLSearchParams 使用)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
const axios = require('axios'); const { URLSearchParams } = require('url'); async function exchangeCode(code, code_verifier) { const params = new URLSearchParams(); params.append('grant_type', 'authorization_code'); params.append('code', code); params.append('redirect_uri', process.env.TOGETTER_REDIRECT_URI); params.append('client_id', process.env.TOGETTER_CLIENT_ID); params.append('code_verifier', code_verifier); try { const resp = await axios.post(process.env.TOGETTER_TOKEN_URL, params.toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, timeout: 10000 }); // アクセストークンはログに出さない console.log('token obtained, expires_in=', resp.data.expires_in); return resp.data; } catch (err) { console.error('token exchange error:', err.response ? err.response.status : err.message); throw err; } } |
発行されたレスポンスに access_token のほか refresh_token が含まれるかはサービスにより異なります。レスポンス内の expires_in や refresh_token の有無を必ず確認してください。
Client Credentials フロー:実装サンプルと注意点
サーバ間の定期バッチや ETL は Client Credentials を選びます。
通常、refresh_token は発行されないことが多いです。トークンの有効期間とローテーション設計が重要です。
以下は実運用を想定した curl / Python / Node.js の例とエラー処理例です。
curl での token 取得と API 呼び出し(Client Credentials)
curl で基本的な取得と API 呼び出しを行う例です。
|
1 2 3 4 5 6 7 8 9 10 11 |
# トークン取得(Basic 認証) TOKEN_RESP=$(curl -s -u "${TOGETTER_CLIENT_ID}:${TOGETTER_CLIENT_SECRET}" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ "${TOGETTER_TOKEN_URL}") ACCESS_TOKEN=$(echo "$TOKEN_RESP" | jq -r '.access_token') # API 呼び出し curl -s -H "Authorization: Bearer ${ACCESS_TOKEN}" "${TOGETTER_API_BASE}/v1/summaries?limit=5" |
jq は JSON パース用ツールです。トークン値はログへ平文出力しないでください。
Python の例(requests、例外処理と安全ログ)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import os, requests, logging logger = logging.getLogger(__name__) def get_token(): try: resp = requests.post( os.environ['TOGETTER_TOKEN_URL'], data={'grant_type': 'client_credentials'}, auth=(os.environ['TOGETTER_CLIENT_ID'], os.environ['TOGETTER_CLIENT_SECRET']), timeout=10 ) resp.raise_for_status() data = resp.json() logger.info('token obtained, expires_in=%s', data.get('expires_in')) return data['access_token'] except requests.RequestException as e: logger.error('token request failed: %s', str(e)) raise |
Node.js(axios + URLSearchParams、例外処理、ログの扱い)
Node.js で axios を使う場合は form-encoding を明示し、URLSearchParams を使います。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
const axios = require('axios'); const { URLSearchParams } = require('url'); async function getClientToken() { try { const params = new URLSearchParams(); params.append('grant_type', 'client_credentials'); const resp = await axios.post(process.env.TOGETTER_TOKEN_URL, params.toString(), { auth: { username: process.env.TOGETTER_CLIENT_ID, password: process.env.TOGETTER_CLIENT_SECRET }, headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, timeout: 10000 }); console.log('token obtained, expires_in=', resp.data.expires_in); // トークン本体は出力しない return resp.data.access_token; } catch (err) { console.error('token fetch failed:', err.response ? err.response.status : err.message); throw err; } } |
環境によっては qs パッケージを使うこともありますが、Node.js 標準の URLSearchParams で十分なことが多いです。
API エンドポイント・スコープ・検証手順
ここではエンドポイントやレスポンスの扱い、検証方法、スコープ設計の観点を示します。
示す API パスやフィールドは例示です。実運用前に必ず提供元の最新仕様を照合してください。
レスポンス例と必須/任意フィールドの注記
以下は例示的なレスポンスです。フィールドの必須/任意はサービスごとに異なります。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
{ "data": [ { "id": "123", "title": "まとめタイトル", "description": "概要", "author": { "id": "u1", "name": "著者名", "handle": "@hoge" }, "created_at": "2024-01-01T00:00:00Z", "items": [ { "tweet_id": "111", "tweet_url": "...", "text_preview": "..." } ], "tags": ["tag1"], "counts": { "views": 100, "likes": 5 } } ], "paging": { "next_cursor": "abc" } } |
一般的な確認ポイント(例示):
- 必須である可能性が高い項目: top-level の data 配列、data[].id、data[].title
- 任意となることがある項目: description、items(中身の存在)、tags、counts
- トークン関連: access_token(必須)、token_type(通常 "Bearer")、expires_in(推奨)、refresh_token(任意)
実運用では、レスポンスに含まれる実際のフィールド名と null 許容を確認してください。
curl でレスポンスヘッダを確認する手順(レート/ページング検証)
レスポンスヘッダでレート情報やリクエストIDを確認します。ヘッダ名はサービスにより異なります。
基本コマンド(ヘッダを標準出力に出す)
|
1 2 |
curl -s -D - -H "Authorization: Bearer ${ACCESS_TOKEN}" "${TOGETTER_API_BASE}/v1/summaries?limit=1" -o /dev/null |
代表的に確認するヘッダ例(サービス毎に異なります):
- X-RateLimit-Limit / X-RateLimit-Remaining / Retry-After
- Request-Id / X-Request-Id(問い合わせ時に提示するため保存)
- Link(ページング用の next URL が付く場合あり)
コマンド例(ヘッダとボディ両方取得)
|
1 2 3 |
curl -s -D headers.txt -H "Authorization: Bearer ${ACCESS_TOKEN}" "${TOGETTER_API_BASE}/v1/summaries?limit=5" -o body.json # headers.txt を確認して、rate limit のヘッダ名・値を確認する |
ヘッダ名は環境で必ず確認し、監視ルールに反映してください。
スコープ設計と代表例(最小権限)
代表的なスコープ例(あくまで例示):
- summaries.read — まとめの取得(最小読み取り)
- summaries.write — まとめの作成・編集
- summaries.delete — まとめの削除
- users.read — ユーザー情報取得
- tweets.read — ツイート参照(X/Twitter 経由の権限)
- offline_access — リフレッシュトークン発行を要求する場合の例示スコープ
最小権限設計の実例: 一覧表示だけなら summaries.read のみを要求します。ユーザーが編集する場合のみ summaries.write を要求します。refresh_token 発行要否はサービス仕様に依存します。
運用・セキュリティ・法務(本番移行チェックリスト含む)
本番運用ではシークレット管理、キーのローテーション、漏洩時対応、法務対応を具体化しておく必要があります。
以下に CI/CD の運用例、ローテーション手順、漏洩時フロー、保存期間と削除自動化の例を示します。
CI/CD とシークレット管理
CI/CD では以下の対策を推奨します。
- シークレットは AWS Secrets Manager / GCP Secret Manager / Azure Key Vault 等で管理する。
- リポジトリに平文を置かない。環境変数はデプロイ時に注入する。
- GitHub Actions 等では OIDC を使った短期間のトークン発行を利用する。
- ログ出力時はアクセストークンや client_secret をマスクするユーティリティを用意する。
実装ポイント(例):
- デプロイ時: Secret Manager から取得してプロセス環境に注入。
- アプリ内: トークンはメモリにのみ保持し、永続化が必要なら暗号化ストレージを使う。
キーのローテーションと漏洩時対応フロー
推奨ローテーション手順(高レベル):
- 新しい client_secret を発行(管理コンソール)。
- 新シークレットを Secret Manager に書き込み、ステージングで検証。
- 本番にデプロイして動作確認。
- 古いシークレットを無効化・削除。
- ローテーション記録を監査ログに残す。
漏洩時の優先対応フロー(例):
- 影響範囲の特定(どのサービス・トークンが流出か)。
- 即時ローテーション(新しい secret を発行・適用)。
- 該当トークンの失効・権限停止。
- ログ収集・フォレンジック解析。
- 関係者への初期通知(セキュリティチーム、プロダクト、法務)。
- 必要に応じて提供元(Togetter)へ報告。
通知の優先度や SLA は事業体により定めてください。初期通知は 24 時間以内を目安にする企業が多いです。
保存期間・削除要求の自動化と契約上の注意点
保存期間の実務例(例示):
- 一般公開されるまとめのメタデータ: 365 日(例)
- ユーザーの個人データ(プロフィール等): 90 日(法令・契約に応じて短縮)
- 監査ログ: 30〜90 日(要件に応じる)
削除要求の自動化パターン:
- API/Webhook で削除要求を受け取る → レコードに削除フラグを付与 → 検索インデックスから除外 → バックグラウンドで完全削除(一定期間経過後)
- 削除時は監査ログを残すが、個人データは不可逆に消去する運用を設計する
契約上の注意点:
- 再配布・二次利用は契約で明示されているか確認する。
- データ保持、SLA、責任分界点(責任範囲)を営業窓口と契約書で明確にする。
- ブランド利用(ロゴや社名)のルールは公式ブランドガイドに従う。ロゴ利用や公式窓口の問い合わせ方法は開発者ポータルで確認してください。
本番移行チェックリスト(要点)
本番移行前に最低限確認すべき項目です。
- 環境変数と Secret Manager の設定(dev/stg/prod 分離)
- リダイレクトURI、IP 制限、CORS 設定の検証
- レート制限モニタリングとアラート(429/5xx)設定
- ログの相関ID、レスポンスヘッダの記録・保存場所設定
- パフォーマンステストとステージングでのレートテスト
- 法務チェック(保存期間、再配布条件、利用規約)
- キー漏洩時のローテーション手順と連絡フローの確認
FAQ(短い Q&A)
よくあるトラブルと初動対応を短く示します。
401 が返る場合
トークン期限切れやスコープ不足が多い原因です。expires_in を確認し、必要なら再認可または refresh_token による更新を試してください。
403 が返る場合
付与したスコープと API 操作が合致しているかを確認してください。ユーザー固有データは追加同意が必要な場合があります。
429 が頻発する場合
キャッシュ、バッチ化、指数バックオフ、ジッターの導入を検討してください。必要なら営業窓口でレート枠の拡張相談を行ってください。
ログにアクセストークンを出してしまった場合
速やかに該当トークンを無効化し、該当トークンを使用していたシステムのトークンをローテーションしてください。影響範囲を調査し、必要に応じて提供元へ報告します。
まとめ(要点)
- Togetter API は「まとめ」単位のデータ取得に特化します。OAuth2 の選択とスコープ設計が運用の鍵です。
- Authorization Code はユーザー代理、PKCE は SPA/モバイルで必須に近い対策です。Client Credentials はサーバ間処理向けです。
- サンプルコードは curl/Python/Node.js を用意しました。実運用では必ず秘密情報を Secret Manager で管理してください。
- エンドポイント名・スコープ・ヘッダ名は例示です。実際の仕様は提供元の公式ドキュメントで確認してください。
- 本番移行ではシークレット管理、ローテーション、漏洩対応フロー、法務チェックリストを整備してください。
重要事項の再掲: この記事で示したエンドポイント名やレスポンス、スコープ名はあくまで例示です。実装前に Togetter の公式開発者ドキュメントで最新仕様(エンドポイント、必須フィールド、レート制限ヘッダ、スコープ名、トークンポリシー等)を必ず確認してください。