認証付き社内Webアプリの一部だけを社外へ匿名公開する(前段ゲートウェイがある構成の落とし穴)
トンネル公開している社内アプリに認証不要の画面を1つだけ足す手順。前段ゲートウェイが /api/* を一律認証していると本体だけ直しても外部は401になり、しかもlocalhost検証では200で通ってしまう。素通し条件の書き方、公開APIの返却フィールド設計、公開URLに対する自動検証5項目まで。
約3.9万トークンの節約 (API料金換算で約59円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。
この巻物について
「認証付き社内Webアプリの一部だけを社外へ匿名公開する(前段ゲートウェイがある構成の落とし穴)」は、Web開発カテゴリのAI指示書(MDファイル)です。トンネル公開している社内アプリに認証不要の画面を1つだけ足す手順。前段ゲートウェイが /api/* を一律認証していると本体だけ直しても外部は401になり、しかもlocalhost検証では200で通ってしまう。素通し条件の書き方、公開APIの返却フィールド設計、公開URLに対する自動検証5項目まで。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約3.9万トークン(API料金換算で約59円)・87%のトークンを節約できます。
- カテゴリ
- Web開発
- 対応AI
- claude-code、cursor、codex-cli
- ライセンス
- 商用利用可 (再販不可)
- 価格
- 無料
- ゼロから開発時
- 約4.5万トークン
- この巻物使用時
- 約6,000トークン
- 節約量
- 約3.9万トークン (約59円)
- 更新日
- 2026-09-17
使い方 (AIに渡す3つの方法)
いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。
claude "https://makimono-md.vercel.app/api/v1/files/web-3/raw を読み込んで、この指示書どおりに実装して"
中身
認証付き社内Webアプリの一部だけを社外へ匿名公開する(前段ゲートウェイがある構成の落とし穴)
社内向けに作った認証必須の Web アプリの中に、社外の人へ渡したい画面が1つだけ出てくることがある(空き日程の案内、受付フォーム、ステータス確認など)。 このとき「本体アプリに認証不要のルートを1本足す」だけでは外部から動かない構成がある。前段にゲートウェイ(リバースプロキシ役のアプリ)が挟まっている場合で、しかもローカル検証では成功してしまうため気づきにくい。
対象読者: 自宅/社内PCのアプリをトンネル(Tailscale Funnel、Cloudflare Tunnel、ngrok 等)で公開している人。
1. まず構成を実測する。README を信じない
公開URLが本体アプリに直結しているとは限らない。必ずトンネルの転送先を実測してから設計する。
# Tailscale Funnel の場合
tailscale serve status
https://<公開ホスト> (Funnel on)
|-- / proxy http://localhost:3939 # ← 本体が 3940 でも、ここが 3939 のことがある
この例では 3939 に別のゲートウェイアプリがいて、そこから本体(3940)へ中継している。 リポジトリの README に「トンネルは本体ポートに向く」と書かれていても、運用の途中でゲートウェイが挟まって README だけ古いままというのはよくある。実測値を正とする。
ゲートウェイの典型的な作り(Next.js の例):
- 非 API パスは rewrite で本体へ素通し
- API パスだけは自前のハンドラを通し、そこで認証してから本体へ中継する(本体のトークンをサーバ側で付け直す)
// gateway/next.config.js
async rewrites() {
return [
{ source: '/app', destination: 'http://localhost:3940/app' },
// api 以外は本体へ直接。api は下のハンドラに落とす
{ source: '/app/:path((?!api(?:/|$)).*)', destination: 'http://localhost:3940/app/:path*' },
];
}
2. 落とし穴: 本体だけ直しても外部からは 401
本体アプリに認証不要のルート /app/api/public/... を新設しても、ゲートウェイの中継ハンドラが /app/api/* を無条件に認証していると、外部URLでは 401 になる。
なぜ気づきにくいか
| 叩き先 | 結果 |
|---|---|
http://localhost:<本体ポート>/app/api/public/... | 200(ゲートウェイを経由しないため) |
https://<公開ホスト>/app/api/public/... | 401(ゲートウェイで止まる) |
公開ページ自体は rewrite で素通しなので 200 で開ける。だから「ページは表示される、でも中身が出ない」という壊れ方をする。 ローカル検証だけで「本番OK」と判断すると確実に見落とす。AI コーディングエージェントに任せた場合も、localhost の 200 を根拠に完了報告してくることがある。
3. 直し方: ゲートウェイ側に「公開パスの素通し」を1箇所だけ書く
素通しはパス接頭辞 + 読み取りメソッドの2条件で絞る。パスだけで開けると、同じ接頭辞下の書き込みまで公開される。
// gateway/app/<本体basePath>/api/[...path]/route.ts
async function proxyRequest(req: NextRequest, context: { params: { path: string[] } }) {
// /<basePath>/api/public/* は社外向けの公開エンドポイント。
// ゲートウェイの認証は通さず読み取りだけ素通しする。書き込みは従来どおり認証必須。
const isPublicRead = context.params.path[0] === 'public'
&& (req.method === 'GET' || req.method === 'HEAD');
if (!isPublicRead) {
const auth = checkAuth(req);
if (!auth.ok) return NextResponse.json({ error: auth.message }, { status: auth.status });
}
// 以降は従来どおり本体へ中継(本体用トークンをサーバ側で付け直す)
}
修正後はゲートウェイを再ビルドして再起動すること(ポートは変えない、本体プロセスは止めない)。ここを忘れると直したのに 401 のままになる。
4. 公開APIが返してよいものを先に決める
「認証を外す」と「情報を出す」は別の判断。公開エンドポイントはホワイトリストで組み立てる(内部の型をそのまま返さない)。
- 返す: 求められた答えそのものだけ(例: 日付と時間帯)
- 返さない: 予定や案件のタイトル、顧客名・個人名、場所、会議URL、件数、内部ID、キャッシュの更新時刻やファイルパス、エラーの内部事情
- 「該当なし」の理由も返さない。理由を返すと外から内部状態を推測できる。該当しない行はレスポンスから落とす
- エラー本文は利用者向けの固定文言にし、サーバのエラー文字列を画面に出さない
- 簡易レート制限を入れる(プロセス内 Map で IP ごと60秒 N 件など。完全でなくてよい)
return Response.json({
items: publicItems, // 必要最小限のフィールドだけ詰め替えたもの
text: formatForHumans(), // そのままコピーして相手に貼れる整形済みテキスト
generatedAt: nowIso(),
freshness: isStale ? 'stale' : 'ok',
});
社外の人がそのまま相手に貼れる text を一緒に返すと、受け取った側の手間が減る。
5. 検証は必ず公開URLに対して行う
localhost の 200 を本番の証拠にしない。 最低限これだけは公開URLに対して自動で確かめる。
const BASE = 'https://<公開ホスト>';
const FORBIDDEN = ['summary', 'location', 'customerName', 'eventsCount']; // 出てはいけないキー
// 1. 公開API: Cookie も Authorization も付けずに 200、かつ機微キーが出ない
// 2. 既存の認証API: 認証なしで 401(公開化が波及していない)
// 3. 公開APIへの POST: 401 か 405(書き込みを素通ししていない)
// 4. 公開ページ: 200 で、想定の見出し文字列を含む
// 5. 既存トップ: 200(他の画面を壊していない)
fetch(url, { redirect: 'manual' }) で叩き、レスポンス本文に "<禁止キー>" が含まれないことを機械的に判定する。目視確認にしない。
2 と 3 を省くと、「公開したつもりが全体を開けてしまった」事故に気づけない。
6. チェックリスト
- トンネルの転送先を実測した(README ではなく
serve status等の出力で) - 前段ゲートウェイの有無を確認した
- ゲートウェイ側に公開パスの素通しを書いた(接頭辞 + GET/HEAD のみ)
- ゲートウェイを再ビルド・再起動した
- 公開APIの返却フィールドをホワイトリストで組んだ
- 「該当なし」の理由を返していない
- レート制限を入れた
- 公開URLに対して 5 節の 1〜5 を全部 PASS させた
- 公開URLを知っていれば誰でも見られることを、依頼者に明示して合意した
- README に「トンネル→ゲートウェイ→本体」の実際の経路とポートを書いた
補足: そもそも公開してよいか
URL を知っていれば誰でも開ける=リンクが外部に転送されれば第三者も見られる、ということ。 「社外の人に渡したい」と「インターネットに公開してよい」は別なので、依頼者に何が出るかを具体的に示して合意を取ってから公開する。 合言葉を1つ挟むだけでも、無差別のアクセスは実用上ほぼ止まる(強度は求めない、誤爆防止の意味)。
よくある質問
+「認証付き社内Webアプリの一部だけを社外へ匿名公開する(前段ゲートウェイがある構成の落とし穴)」とは何ですか?
トンネル公開している社内アプリに認証不要の画面を1つだけ足す手順。前段ゲートウェイが /api/* を一律認証していると本体だけ直しても外部は401になり、しかもlocalhost検証では200で通ってしまう。素通し条件の書き方、公開APIの返却フィールド設計、公開URLに対する自動検証5項目まで。
+どれくらいトークン(費用)を節約できますか?
ゼロから開発すると約4.5万トークンかかりますが、この巻物を使えば約6,000トークンで済みます。差し引き約3.9万トークン(API料金換算で約59円)・87%の節約です。
+どうやって使いますか?
無料です。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実描画)の受け入れテストまで含む。
この巻物、誰かのトークンも救えます
𝕏 で節約レシートをシェア