ローテーションするOAuth2リフレッシュトークンをDB永続化して壊れないようにする型
freee/Google等でリフレッシュトークンがローテーションし env固定だと初回更新後に必ず壊れる問題を、トークンをDBに持ち更新のたび書き戻す型で解決。マスタID取得・冪等ガード・段階導入まで含む会計API連携の定石。
約7,500トークンの節約 (API料金換算で約11円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。
この巻物について
「ローテーションするOAuth2リフレッシュトークンをDB永続化して壊れないようにする型」は、Web開発カテゴリのAI指示書(MDファイル)です。freee/Google等でリフレッシュトークンがローテーションし env固定だと初回更新後に必ず壊れる問題を、トークンをDBに持ち更新のたび書き戻す型で解決。マスタID取得・冪等ガード・段階導入まで含む会計API連携の定石。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約7,500トークン(API料金換算で約11円)・83%のトークンを節約できます。
- カテゴリ
- Web開発
- 対応AI
- claude-code、cursor、codex-cli
- ライセンス
- 商用利用可 (再販不可)
- 価格
- 無料
- ゼロから開発時
- 約9,000トークン
- この巻物使用時
- 約1,500トークン
- 節約量
- 約7,500トークン (約11円)
- 更新日
- 2026-08-20
使い方 (AIに渡す3つの方法)
いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。
claude "https://makimono-md.vercel.app/api/v1/files/oauth2-db/raw を読み込んで、この指示書どおりに実装して"
中身
ローテーションする OAuth2 リフレッシュトークンを DB 永続化して壊れないようにする型
freee・Google 等の OAuth2 API 連携で「最初は動くのに数時間〜翌日に必ず認証エラーになる」典型バグの回避策。Web アプリ(Next.js/Node 等)に外部会計・SaaS API を繋ぐときの定石。
前提となる落とし穴
多くの OAuth2 プロバイダ(freee 等)は リフレッシュトークンがローテーションする=アクセストークンを更新するたびに新しいリフレッシュトークンが発行され、古いリフレッシュトークンは即失効する。
- リフレッシュトークンを環境変数(
REFRESH_TOKEN=...)に固定で置くと、1回目の更新後に env の値が無効化され、次回のトークン更新がinvalid_grantで失敗する。 - サーバレス(複数インスタンス)では特に、どのインスタンスが更新しても env は書き換わらないので確実に壊れる。
- 症状:「前に渡したトークンが失効していた」「初日は動いたが翌朝から連携エラー」。
解決の型:トークンを DB に持ち、更新のたびに書き戻す
- トークン保管テーブルを作る(単一行)。
client_id/client_secret/refresh_token/access_token/access_expires_atを保持。client_id/secret も同居させると、この API 用の環境変数依存をゼロにできる(秘匿値を env に置けない/CI classifier に止められる環境で有効)。
create table oauth_tokens (
id text primary key, -- 'default'
client_id text not null,
client_secret text not null,
refresh_token text not null,
access_token text,
access_expires_at timestamptz,
updated_at timestamptz not null default now()
);
getValidAccessToken()を1本用意:期限内ならキャッシュを返す。期限切れならリフレッシュし、返ってきた新 refresh_token を必ず DB に書き戻す。
export async function getValidAccessToken(): Promise<string> {
const row = await loadTokenRow(); // DB から1行
if (row.accessToken && row.accessExpiresAt.getTime() > Date.now() + 120_000) {
return row.accessToken; // まだ有効
}
const res = await fetch(TOKEN_URL, {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
client_id: row.clientId,
client_secret: row.clientSecret,
refresh_token: row.refreshToken,
}),
});
if (!res.ok) throw new Error(`token refresh failed (要再認証): ${res.status}`);
const j = await res.json(); // { access_token, refresh_token, expires_in }
await saveTokenRow({ // ★ 新 refresh_token を必ず永続化
refreshToken: j.refresh_token,
accessToken: j.access_token,
accessExpiresAt: new Date(Date.now() + j.expires_in * 1000),
});
return j.access_token;
}
- HTTP アダプタはトークンを受け取るだけにする(トークン管理を混ぜない)。レイヤ規約(adapters は DB/サービスを import しない)とも整合する。
export async function apiFetch<T>(path, init, accessToken): Promise<T> { /* Bearer 付与のみ */ }
初回接続(トークンの種を入れる)
- リフレッシュトークンは 一度だけ OAuth 認可フローで取得する(これは人手が要る正規の1操作)。
authorize URL(
redirect_uri=urn:ietf:wg:oauth:2.0:oobにすると画面にコードが出る CLI 向け)を開く→ログイン&許可→表示された 認可コード を1回受領→grant_type=authorization_codeでトークン交換→DB にシード。 - 以後はローテーションを DB が吸収するので、再認可は原則不要(リフレッシュトークン自体の有効期限切れ時のみ再実行)。
実装前に必ずやること:マスタ ID を「取得」する(推測しない)
会計 API 等は勘定科目・税区分・品目などを ID/コードで指定する。名前からの推測は事故る。
- 勘定科目:
GET /account_itemsを叩き、運用担当(経理)に「この処理はどの科目か」を実在する科目名の一覧から選んでもらう。担当が言う名前(例「仕入・原価」)が API 上に存在しないことは普通にある。 - 税区分:
GET /taxes/codesでその事業所の実コードを取得(コードは事業所ごとに異なる)。インボイス経過措置は控除率が枝番(控80%/控50%…)で分かれ、時期で変わる点に注意(切替日をコード側に反映する運用を決めておく)。 - 会社/事業所 ID:
GET /companiesで API 用の ID を確定(画面表示の「事業所番号」と API の company_id は別物のことがある)。
二重登録の防止
会計への書き込み(取引登録)は 冪等ガード必須。
- 「同一案件で成功済みなら再実行を拒否」を DB で判定。
- 非冪等 POST(取引作成)は 失敗時に自動リトライしない(コミット後の 5xx/タイムアウトで二重登録になる)。リトライは 401(副作用前のトークン失効)だけに限定するか、人手の再送に寄せる。
- 既定は DRY-RUN(実書き込みしないシミュレーション)にし、本番書き込みは管理者権限+段階導入(テスト1件を実登録して担当が実画面で確認)。
チェックリスト
- リフレッシュトークンを env でなく DB に置き、更新のたびに書き戻す
- access_token は期限+マージンでキャッシュ
- マスタ(勘定科目/税区分/会社ID)は API から取得し、担当に実在名から選定させる
- 取引登録は冪等ガード+非冪等 POST は無闇にリトライしない
- 既定 DRY-RUN、本番は管理者のみ+テスト1件で実画面確認
よくある質問
+「ローテーションするOAuth2リフレッシュトークンをDB永続化して壊れないようにする型」とは何ですか?
freee/Google等でリフレッシュトークンがローテーションし env固定だと初回更新後に必ず壊れる問題を、トークンをDBに持ち更新のたび書き戻す型で解決。マスタID取得・冪等ガード・段階導入まで含む会計API連携の定石。
+どれくらいトークン(費用)を節約できますか?
ゼロから開発すると約9,000トークンかかりますが、この巻物を使えば約1,500トークンで済みます。差し引き約7,500トークン(API料金換算で約11円)・83%の節約です。
+どうやって使いますか?
無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。
+どのAIツールに対応していますか?
claude-code、cursor、codex-cli に対応しています。
+商用利用できますか?
ライセンスは「商用利用可 (再販不可)」です。
🤝 自分でAIを動かすのは、まだ不安…という方へ
この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。
関連する巻物
Next.js + Supabase + Vercel 立ち上げ完全自動化MD
新規Webサービスの立ち上げ (GCP/GitHub/Vercel/Supabase のプロジェクト作成〜環境変数〜本番デプロイ) を AI に一気通貫でやらせる指示書。人間の作業はログイン1回だけ。
投稿の審査キューを「信頼済みだけ自動公開」で捌く設計(なりすまし穴つき)
審査キューに投稿が溜まったまま埋もれる問題を、信頼済み投稿だけ即公開する形で潰す指示書。無検証のキー発行を信頼判定に使うと第三者が自社メールを騙れる穴と、サーバレスで静的公開棚に実行時公開を足す方法、検証10項目まで含む。
DB型サイトを「一覧だけ会員限定・個別ページは残す」に切り替える指示書(Next.js App Router)
自社DBで集客していたサイトが競合のリスト抜き取りに気づいた時の改修手順。名前が並ぶバルクな一覧だけを会員限定にし、個別ページはtitle/H1とCTAを残す。ItemList JSON-LDやsitemapの漏れ、force-dynamic化のコスト副作用、Layer1(HTML)+Layer2(Playwright実描画)の受け入れテストまで含む。
この巻物、誰かのトークンも救えます
𝕏 で節約レシートをシェア