← 一覧に戻る

Zaim 認証トークン定期更新 (rotate) 運用設計

2026年5月7日 8:35 更新

一言でいうと

Zaim連携の鍵を半年に一度安全に交換する仕組み。

なぜこれが必要なのか

家計簿サービス Zaim と自作 Cloudflare Workers (claude-code-zaim) の連携には「access_token」(アクセストークン: サービスにログイン代行する電子的な合鍵)を使ってる。Zaim の access_token は OAuth 1.0a (オーオース1.0a: 鍵の交換規格の一種) で発行され、明示的に無効化しない限り永久に有効という設計。

これは便利な反面、万が一トークンが漏れた場合 (例: 公開リポジトリへの誤コミット、PCの紛失) に攻撃者が無期限でZaimの家計データを読み書きできてしまう。インシデント発生時の被害は時間とともに大きくなる。

そこで「定期的に鍵を交換する」運用ルール (rotate: 計画的な鍵交換) を整備した。漏洩窓 (鍵が無防備にさらされる期間) を 最大6ヶ月 に圧縮する。

放置した場合のリスク: 過去のコミット履歴・dotfiles バックアップ・古いログ・退役したPCに残った .dev.vars (環境変数ファイル) など、人間が把握しきれない場所からトークンが漏れる経路は無数に存在。漏れた瞬間に永久攻撃可能、というのは家計データを扱うアプリとして設計上のリスク。

何をしたのか

3つの成果物 + 1つの拡張で rotate 運用を仕組み化した。

① rotate 手順書 (markdown)

~/ghq/github.com/ramenumaiwhy/claude-code-zaim/docs/zaim-token-rotate.md

手動でも実行できるよう、step-by-step で全コマンドと期待結果を明記。失敗時のリカバリ表もここに集約。

② 対話的 rotate スクリプト (bash)

~/secretary-state/zaim-token-rotate.sh

手順書の内容をスクリプト化。各ステップで確認プロンプトを出し、Ctrl-C でいつでも安全に中断できる。後述の「安全装置」が組み込まれている。

③ plan-viewer HTML (この記事)

~/plan-viewer/2026-05-07_zaim-token-rotate.html

スマホから手順とチェックリストを確認できるようにした。実行当日に手元で見ながら進めるための表示用。

④ oauth-pin-flow.ts への verify サブコマンド追加

claude-code-zaim/scripts/oauth-pin-flow.ts に verify サブコマンドを実装。

既存の認証フロー実装ファイルを 1 サブコマンド分だけ拡張。新トークンが本当に Zaim API で動くかを /v2/home/user/verify (Zaim 標準のトークン検証エンドポイント) で確認するためのもの。新トークン上書き前の最終チェックに使う。

rotate のサイクル推奨

用途周期理由
通常運用 (推奨) 6ヶ月 OAuth 1.0a の永続トークンは expire しない (期限切れにならない) ので、運用負荷と漏洩窓のバランス点。家計簿1個人運用ではこの粒度で十分
高セキュリティ要件時 3ヶ月 端末紛失や dotfiles 公開リポジトリへの事故などが懸念される時期は短縮
インシデント時 即時 漏洩疑い・誤コミット発覚時はこの手順書を即実行する

rotate のステップ (8段階)

0前提チェック
bun (高速 JavaScript ランタイム), npx wrangler (Cloudflare の CLI ツール), .dev.vars ファイル (consumer_key などを保存した環境変数ファイル) の存在を確認。1つでも欠けていたらここで停止する。
1現役 Workers Secret の確認
本番環境 (Cloudflare Workers) に登録されている現役トークンの最終更新日を確認する。これは「いつから使ってるか」のメモのため。
2新トークン発行 - request phase
Zaim サーバーから一時的な request_token (請求トークン) を取得し、認可URLをブラウザで開く。Zaim にログインして「許可」を押すと verifier (認証完了を証明する短いコード) が画面に表示される。それをコピーしてターミナルに貼り付ける。
3新トークン発行 - access phase
verifier を使って新しい access_token と access_token_secret を発行する。スクリプトが自動で標準出力から抽出して内部変数に保存する (この時点ではまだ Workers には書いていない)。
4新トークン単体検証 (verify)
新トークンを使って Zaim の /v2/home/user/verify エンドポイント (トークン有効性を確認するための専用窓口) を叩く。200 OK + ユーザー ID が返れば正常。失敗した場合はここで abort し、Workers Secret は変更されない (旧トークン無傷)
5Workers Secret 上書き
wrangler secret put コマンドで Cloudflare Workers の環境変数 ZAIM_ACCESS_TOKEN と ZAIM_ACCESS_TOKEN_SECRET を新値に置き換える。確認プロンプトに「y」で進める。
6本番疎通確認
Workers にデプロイされた API (例: /api/zaim/accounts) を実際に 1 回叩いて、新トークンで本番が動くことを確認する。
7旧トークン無効化 (ブラウザ手動操作)
Zaim には API での access_token revoke (取消) が無い。https://auth.zaim.net/users/me/services をブラウザで開いて、rotate 前の app セッションを「連携解除」する (ここはユーザーが目視で行う)。
8ログ追記
~/secretary-state/logs/zaim-token-rotate.log に rotate 完了日時とトークン先頭 8 文字を追記。次回 rotate 期日 (6ヶ月後) を Apple Reminders「中長期」リストに手動で登録する。

安全装置の設計意図

装置1: verify 通過まで Workers Secret は触らない — Step 4 で新トークンの動作確認をしてから Step 5 に進む。verify が 401 や 403 (認証拒否) を返したら abort、旧トークンは Workers 上にそのまま残る。これにより「新トークン取得には成功したが実は壊れていた」というシナリオでもサービス停止を防ぐ。
装置2: revoke は手動 (ブラウザ操作) — Zaim には access_token を API で取り消す手段が無い。スクリプト側で「自動 revoke」を試みると誤って Workers Secret を削除する事故につながりかねない。なので script は revoke URL を表示するだけで実行はせず、人間が Zaim 公式 UI から確認しつつ操作する。
装置3: 新旧並存期間がある — Step 5 完了から Step 7 完了までの間、新トークンと旧トークンの両方が valid (有効) になる。Zaim は同一 consumer (同じアプリ) に対する複数 access_token を許容するため、本番が新トークンに完全切り替わってから旧トークンを無効化してもサービスは止まらない。これは「新トークンで本番動かしてから旧を切る」という安全な順序を実現するための仕様。

実行当日のチェックリスト

失敗時のリカバリ

症状原因候補対応
request で 401consumer_key/secret 間違い.dev.vars を再確認
access で 401verifier 入力ミス・期限切れrequest から再実行
verify で 401access フローでトークン取得失敗 / consumer 不一致request から再実行
Workers Secret 更新後に本番 401wrangler secret put の値に余分な改行が混入echo -n または printf '%s' で改行除去して再投入
旧 app 連携解除後も旧トークンで 200Zaim 側のキャッシュ反映待ち数分待って再確認、それでも継続なら Zaim サポートに問い合わせ

ポイント・補足

見送った選択肢: 自動 rotate (cron で半年に1回スクリプト発火) は採用しなかった。理由は (a) ブラウザでの認可操作 (verifier コピー) が必須で完全自動化できない、(b) 失敗時に深夜だと気づけずサービス停止の可能性、の2点。手動運用 + Reminders 通知の方が安全。

Zaim API の制約: OAuth 1.0a は OAuth 2.0 と違って refresh_token (期限切れ時に自動更新するための予備トークン) が無く、毎回ゼロから認可フローを回す必要がある。これは Zaim 側の実装制約なので、自分側で工夫してもこの手順を短縮できない。

新旧並存期間の検出方法: Step 7 で「どれが新セッションか分からない」場合は、解除前にもう一度 verify でアクセスし、Zaim 連携アプリ画面の「最終アクセス」時刻を見比べる。新しい方を残す。

初回 rotate: この設計を作った段階ではまだ実 rotate は未実施。次のアクション = 健人と一緒に初回 rotate を流して挙動を確認する。Step 4 (verify) と Step 5 (wrangler secret put) の確認プロンプトは特に注意して見ること。

📝 質問モード — テキストを選択してね
✓ 質問を送信しました