ClaudeにOAuth実装を任せた公開実例 — Cloudflareのコミット履歴を読む
CloudflareのOAuth認可ライブラリはClaudeへの1プロンプトから生まれ、依頼文と失敗がコミットに残っています。何を頼み、どこを人間が直したかを追います。
CloudflareのOAuth認可ライブラリ@cloudflare/workers-oauth-providerは、最初のコミットからClaudeへのプロンプトで書かれました。開発者のCloudflareエンジニアは、依頼文も、Claudeが壊した場面も、人間が手で直した理由もコミットメッセージに残しています。HTTP APIとリモートMCPサーバーにOAuth 2.1の認可を足すこのライブラリを手がかりに、Claudeにセキュリティコードを書かせた実務の中身を追います。
workers-oauth-providerとは — Cloudflare製のMCP認可ライブラリ
@cloudflare/workers-oauth-providerは、Cloudflare Workers上でOAuth 2.1の認可サーバーを実装するnpmパッケージです。READMEによれば、HTTP APIとリモートMCPサーバーの両方に認可を追加する用途で作られています。
リポジトリの規模
コミット数
300件超
mainブランチ。マージコミットを含む
スター
約1.9k
フォークは137件
最初のコミット
2025年2月27日
npm初公開は3月16日
現行の版
1.2系
1.0は2026年9月24日
このライブラリが独特なのは機能ではなく、生まれ方が記録として残っている点です。READMEとは別のHISTORY.mdに、開発者自身の経緯が書かれています。
冒頭は「このライブラリ(スキーマのドキュメントも含む)は、大部分をClaudeの助けを借りて書いた」という一文です。Claudeの出力はCloudflareのエンジニアが、セキュリティとRFC準拠に注意して徹底的にレビューしたとも書かれています。初期出力への改善も、多くはClaudeへのプロンプトで重ねたそうです。
意外なのは動機です。開発者は2025年1月の時点ではAI懐疑派で、LLMは「コードを理解しないマルコフ連鎖の生成器」だと考えていました。ひどいコードが出てきて笑えるだろうと思って始めたところ、出てきたコードは悪くなく、直させればそのとおり直った、と書いています。コミット履歴の初期を読んでほしいという誘い文句がHISTORY.mdの末尾に添えられており、本記事もそこを読み進めます。
Cloudflare MCPサーバーの一覧のようにClaudeからMCPサーバーを操作する場面だけでなく、リモートMCPサーバーの認可を実装するライブラリの側にもClaudeが関わっています。
最初のプロンプトは何を要求していたか
最初のコミットは2025年2月27日、コミットメッセージは「Have Claude write an OAuth provider implementation.」です。本文には、実際にClaudeへ送った依頼文がそのまま残っています。
依頼文は、TypeScriptライブラリであることと、Cloudflare Workersで動くことを述べたあと、利用側のコード例を先に示しています。OAuthProviderをexport defaultし、apiRoute・apiHandler・defaultHandler・authorizeEndpoint・tokenEndpoint・clientRegistrationEndpointを設定で受け取る形です。完成後のAPIの見た目を先に書いてから、中身を頼んでいます。
続いて、コード例のあとに箇条書きで実装上の制約を並べています。
- ストレージにはWorkers KVの名前空間
OAUTH_KVを使う - トークンの有効期限はKVのTTL機能で実装する
- トークン文字列そのものは保存せず、SHA-256のハッシュから作った「トークンID」だけを保存する
- クライアントはclient_secretで認証する(Webアプリ想定で、secretを安全に保持できる前提)
ほかに、クライアント登録の作成・一覧・変更・削除と、ユーザーごとの認可の一覧・取り消しをAPIとして用意することも求めています。最後の一文は「Can you please write this library?」でした。
この依頼文の特徴は、設計判断が先に決まっていることです。保存方式・期限管理・クライアント認証の型は人間が決め、Claudeには実装を頼んでいます。
18日間の流れ — 初回プロンプトからnpm公開まで
最初のコミットからnpm公開までは18日で、この間に74件のコミットが積まれました。そのうちClaudeへの依頼コミットが38件、人間が手で直したコミットが19件です。
初回プロンプトからnpm公開まで
- 2025年2月27日1本のプロンプトで骨格を生成
初回プロンプトと、続くストレージスキーマの文書化もClaudeに依頼しました。
- 2025年2月28日チャットで追加要件を重ねる
Node.jsの
cryptoをWebCryptoへ変換し、認可コードとクライアントシークレットもハッシュ化させています。ページネーションの追加では、後述するパース不能の事故が起きました。 - 2025年3月4日Claude Codeへ移行
PKCE対応のコミットに「この時点でチャットセッションからClaude Codeの利用に切り替えた」という注記があります。OAuth 2.0からOAuth 2.1へ進める作業の入口でした。
- 2025年3月5日Claudeが直せないバグを手書きで修正
同じ誤りを繰り返すバグに見切りをつけ、人間が書き直しています。
- 2025年3月7日手書きの仕上げが連続
型の明示、内部メソッドを隠す構造変更、鍵ラップ形式の変更などを「Manually」で始まるコミットで続けています。
- 2025年3月16日npmへ初公開
コミットメッセージは「Initially publishing it as "restricted"」です。
初回プロンプトの翌日の追加指示は、1日で10件を超えています。ストレージ設計の見直しもその中にあり、clients_listやuser_grantsのリストを廃止してKVのlist()に置き換えさせたり、リフレッシュトークンを認可レコードへ統合させたりしています。最初の設計をClaudeに書かせたあとで、データ構造そのものを何度も作り替える使い方です。
Claudeが手こずった場面
コミット履歴には、Claudeが一発で正解を出せなかった場面がそのまま残っています。
代表的なのは構文を壊したケースです。ページネーションの追加を頼んだところ、Claudeはexport class OAuthProvider {の行をファイルの先頭へ移してしまい、全体がパースできなくなりました。直すよう頼むと、最初の応答はさらにコードを混乱させたため、開発者は「この編集は状況を悪化させた。戻ってやり直そう」と、動かす対象と動かしてはいけない範囲を明示して頼み直しています。2回目で問題は直りましたが、頼んでいない宣言の並び替えとドキュメントコメントの追加も入り、getClient()のprivate指定ミスという別のバグも一緒に直っていました。
そのほかにも、コミットメッセージには次のような記録があります。
- シンボルで
getClient()を隠す依頼では、Claudeは最初そのシンボルをexportしようとしました。内部に閉じるのが目的だと指示し直しています propsの暗号化では、Claudeが誤った方向で書き始めたため、開発者が設計上の考慮点を足して頼み直しました- OAuthの仕様を調べさせた場面では、Claudeが最新のOAuth 2.1ドラフトをcurlで取りに行きましたが、Claude Codeのツール側でcurlがブロックされていた、と書かれています
- READMEの更新を頼んだ際、Claudeが自分でコミットしようとしたため、開発者が止めました
- 依存ライブラリのvitestは、Claudeが選んだ版がかなり古かったため更新しています。
npm run testはvitest runに変更されました。既定のwatchモードだとClaude Codeが呼んだときに待ち続けるためです
最後まで直せなかった例もあります。3月5日のコミットは「Claudeは前のコミットにバグを残していた。何度も直すよう頼んだが、同じ間違いを繰り返した」と述べ、この変更は人間が手で書いたと明記しています。
逆に、手書きのほうが速かったと認めるコミットもあります。scopes_supportedの追加では、「手でやったほうが速かった、まあいいか」という趣旨の一文があります。
Claudeに頼んだ作業と、手で書いた作業
Claudeに頼んだ
WebCrypto化、ハッシュ化、ページネーション、PKCE、OAuth 2.1の残り、propsの暗号化、テスト執筆など。要件を文章で渡せば形になる作業です。
手で書いた
型の明示、PImplパターンへの構造変更、鍵ラップ形式の変更、RFC 8414に合わせたメタデータの並び替え、直らないバグの修正など。仕様との照合や構造の判断が絡む作業です。
件数はコミットメッセージの書き出しで数えた値で、1件のコミットに複数の作業が入る場合があります。目安として読んでください。
「vibe codingではない」と言い切る根拠
HISTORY.mdは、この開発がvibe codingではないとはっきり書いています。「すべての行を、そのRFCに経験のあるセキュリティ専門家が、RFCと照合しながら徹底的にレビューした」という趣旨の一文です。
コミットの流れはこの主張と矛盾しません。直らないバグは見切って手書きにし、RFC 8414の項目順にメタデータを並べ直して「レビューしやすくする」コミットまであります。差分を読まずに任せる運用なら、こうしたレビュー用の手直しは出てきません。
公開後に何が変わったか
公開後もClaudeとの付き合いは続いています。2025年4月24日のコミット「Added a new apiHandlers property」には、コードの大半がClaude Sonnet 3.7とClaude Codeで生成されたと書かれています。2026年2月26日には、AIコーディングアシスタント向けにAGENTS.mdが追加されました。
ここが今も変わらない部分です。AGENTS.mdにはこのライブラリの用途が「MCPサーバーの認可」と明記され、MCP仕様の最新の公開版に追従する方針が書かれています。同じ文書は、このライブラリが大部分をClaudeの支援で書かれ、すべてのコードがCloudflareのセキュリティエンジニアにレビューされたと述べています。
一方で、APIの形は大きく変わりました。2026年9月24日の1.0で、認可サーバーとリソースサーバーを別のクラスとして分けるAPI(OAuthAuthorizationServerとOAuthResourceServer)が加わっています。現在のREADMEはこの分割形を推奨しています。初回プロンプトのOAuthProviderは、1つのWorkerが認可サーバーと唯一のリソースを兼ねる形として、引き続きサポートされています。
いまはMCPサーバーの認可をどう実装しているか
導入は次のとおりです。認可サーバー側にはOAUTH_KVという名前のKV名前空間をバインドします。Client ID Metadata Documents(CIMD)を受け付けるなら、global_fetch_strictly_public互換フラグも必要です。
npm install @cloudflare/workers-oauth-provider1つのWorkerで済ませる形は、READMEでは次のように書かれています。初回プロンプトの構造がほぼそのまま残っています。
export default new OAuthProvider<Env>({
apiRoute: '/mcp',
apiHandler: McpApiHandler,
defaultHandler, // your /authorize page
authorizeEndpoint: '/authorize',
tokenEndpoint: '/oauth/token',
scopesSupported: ['mcp:read', 'mcp:write', 'offline_access'],
resourceMetadata: {
resource: 'https://mcp.example.com/mcp',
authorization_servers: ['https://mcp.example.com'],
},
requiredScopes: ['mcp:read'],
clientIdMetadataDocumentEnabled: true,
});apiRouteで保護したいパスを指定すると、そこへ届くリクエストのBearerトークンをライブラリ側が検証し、有効なときだけapiHandlerへ渡します。それ以外はdefaultHandlerに回り、/authorizeの画面はアプリケーション側が書きます。ライブラリ自身はIDプロバイダーではないため、誰がログインしているかの判定は呼び出し側の責任です。
スコープには2つの設定があり、名前が似ています。scopesSupportedは認可サーバーが付与できるものすべてで、requiredScopesはそのリソースへのどのアクセスにも必要なものです。ドキュメントによると、requiredScopesは広告されるだけで強制されません。足りているかを判定するのはハンドラーで、ctx.auth.scopeを見ます。requiredScopesは1.2でresourceMetadata.scopes_supportedの置き換えとして加わり、旧フィールドは動くものの非推奨です。
クライアントが認可サーバーを見つけるまでの流れは、リモートMCPのOAuth認証で扱った401起点の発見と同じです。ライブラリのドキュメントでは次の順に書かれています。
MCPクライアントが認可サーバーに届くまで
- 1
認証なしでMCPエンドポイントを呼ぶ
クライアントが
/mcpへ認証なしのリクエストを送ります。 - 2
401とチャレンジが返る
WWW-Authenticate: Bearer realm="OAuth", resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp", scope="mcp:read"のようなヘッダーが付きます。 - 3
保護リソースのメタデータを取得する
/.well-known/oauth-protected-resource/mcpの文書がauthorization_serversで発行者を示します。 - 4
認可サーバーのメタデータを取得する
選んだ発行者の
/.well-known/oauth-authorization-server(RFC 8414)を読み、認可・トークン交換・登録の場所を知ります。
クライアントがクライアントIDを得る方法は3つあり、対応しているクライアントは事前登録、CIMD、Dynamic Client Registration(DCR)の順に優先します。MCP 2026-07-28仕様ではDCRが新規実装向けに非推奨となり、CIMDが優先経路です。DCRのエンドポイントは、CIMDに対応しないクライアントとの互換のために残されています。パブリッククライアントにはPKCEが必須で、受け付けるのはS256だけです。
保存の面では、アクセストークン・リフレッシュトークン・認可コード・クライアントシークレットはハッシュでのみ保存されます。認可時に渡すpropsは、AES-GCMで暗号化されます。暗号鍵はトークンでラップされるため、有効なトークンの持ち主しか開けません。
注意したいのは、ユーザーIDとメタデータは暗号化されない点です。認可の一覧表示と取り消しに使うためで、ドキュメントはこの2つを「保存領域から見える情報」として扱うよう述べています。人間不在の認証まで踏み込む拡張はMCPのOAuth Client Credentials拡張側の話題です。
初回プロンプトの設計方針が、現在も実装に生きていることが分かります。SHA-256ハッシュだけを保存する方針は、今のストレージスキーマにそのまま残っています。一方でpropsの暗号化は初回プロンプトになく、3月5日に追加依頼したものです。
Claudeにどこまで実装を任せられるか
この実例が示すのは「Claudeに書かせられるか」ではなく、設計・実装・検証のうちどこを人間が持っていたかです。
設計は人間でした。保存方式、期限管理、クライアント認証の型は初回プロンプトに書かれ、propsを暗号化する鍵の扱いも、Claudeが誤った方向へ進みかけたところを人間が考慮点を足して軌道に乗せています。実装はClaudeが担い、追加要件ごとにプロンプトを重ねる形で進みました。検証は人間で、直らないバグの見切りも、RFCとの照合も人間が担っています。
vibe codingの記事は、認証・決済のように重要度の高い処理はvibe codingに向かないと述べています。このライブラリはその領域を、設計の先決め、小さな追加依頼の積み重ね、専門家のレビューという組み合わせで成立させた例です。クライアント側の実装をテストで固める手順はClaude CodeでOAuth実装にあります。
まとめ
認可のような重要度の高いコードでClaudeを使うなら、このコミット履歴が示す要点は2つです。依頼の前に保存方式や認証方式の判断を済ませておくこと、そして同じ誤りを繰り返し始めたら、頼み直しを続けずに手書きへ切り替えること。レビュー担当にRFCを知っている人を置いたことが、全体を支えていました。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- github.com/cloudflare/workers-oauth-provider
- github.com/cloudflare/workers-oauth-provider/blob/main/HISTORY.md
- github.com/cloudflare/workers-oauth-provider/blob/main/README.md
- github.com/cloudflare/workers-oauth-provider/blob/main/docs/authoriza…
- github.com/cloudflare/workers-oauth-provider/blob/main/docs/mcp-disco…
- github.com/cloudflare/workers-oauth-provider/blob/main/AGENTS.md
- api.github.com/repos/cloudflare/workers-oauth-provider/commits