Cloudflare Zero Trust
OK、完全コピペで動く版を作りました。
👉 上から順に貼り付ければ Zero Trust付きSaaSの土台が完成します。
■ 🧩 コピペ用フルコマンド(Windows / Mac共通)
# =========================
# ① プロジェクト作成
# =========================
npm create cloudflare@latest zero-trust-saas
# ※選択
# Hello World Worker
# TypeScript
# Deploy → No
cd zero-trust-saas
npm install
# =========================
# ② Cloudflareログイン
# =========================
npx wrangler login
# =========================
# ③ D1データベース作成
# =========================
npx wrangler d1 create saas-db
# ↓ 出力された database_id をメモ
# =========================
# ④ wrangler.toml編集
# =========================
# 手動で開く(VSCode or メモ帳)
# ↓ これを追加
# [[d1_databases]]
# binding = "DB"
# database_name = "saas-db"
# database_id = "ここに貼る"
# =========================
# ⑤ schema.sql作成
# =========================
echo CREATE TABLE users (id TEXT PRIMARY KEY, email TEXT UNIQUE, plan TEXT DEFAULT 'free', created_at TEXT DEFAULT CURRENT_TIMESTAMP); > schema.sql
# =========================
# ⑥ D1にテーブル反映
# =========================
npx wrangler d1 execute saas-db --file=schema.sql
# =========================
# ⑦ Workersコード書き込み
# =========================
# src/index.ts を開いて全置換
# =========================
# ▼ここからコピペ▼
export default {
async fetch(request, env) {
const email = request.headers.get("cf-access-authenticated-user-email")
if (!email) {
return new Response("Unauthorized", { status: 401 })
}
let user = await env.DB.prepare(
"SELECT * FROM users WHERE email = ?"
).bind(email).first()
if (!user) {
const id = crypto.randomUUID()
await env.DB.prepare(
"INSERT INTO users (id, email, plan) VALUES (?, ?, ?)"
).bind(id, email, "free").run()
user = { id, email, plan: "free" }
}
return Response.json(user)
}
}
# ▲ここまでコピペ▲
# =========================
# ⑧ デプロイ
# =========================
npx wrangler deploy
# ↓ 表示されるURLをコピー
# https://xxxx.workers.dev
# =========================
# ⑨ ローカルテスト(任意)
# =========================
npx wrangler dev
■ 🔐 Zero Trust設定(手動・必須)
-
Cloudflare ダッシュボードへ
-
「Zero Trust」
-
Access → Applications
-
Add application
設定
-
Type:Self-hosted
-
Domain:
xxxx.workers.dev
Policy
-
Action:Allow
-
Include:Emails → 自分のメール
■ 🚀 完成チェック
ブラウザで👇
https://xxxx.workers.dev
👉 結果
-
ログイン画面表示
-
メール認証
-
JSON返る
■ 💥 ここまでで出来てること
✔ ログイン(Zero Trust)
✔ ユーザー自動登録
✔ DB保存(D1)
✔ API
👉 =SaaSのコア完成
■ ⚠ ハマりポイント(事前に潰す)
① DBエラー
👉 env.DB undefined
→ wrangler.tomlミス
② Unauthorized
👉 正常(Zero Trust未設定)
③ email取れない
👉 Access通ってない
■ 🎯 次にやると一気に完成
ここから👇
-
Stripe課金追加
-
Next.jsフロント接続
-
独自ドメイン
■ もし希望あれば
👉 「完全版(課金込みテンプレ)」も作れます
かなり実用レベルになるけど、そこまでやる?
■ 全体の流れ(再確認)
① プロジェクト作成
② D1作成
③ コード配置
④ デプロイ
⑤ Zero Trust接続
■ ① プロジェクト作成(ここがスタート)
コマンド
npm create cloudflare@latest zero-trust-saas
実行後に聞かれる内容
👇 こう答えればOK
Ok to proceed? → y
What would you like to start with?
→ Hello World Worker
Which language?
→ TypeScript
Do you want to deploy?
→ No(あとでやる)
フォルダ移動
cd zero-trust-saas
依存インストール
npm install
■ ② D1データベース作成
D1作成
npx wrangler d1 create saas-db
実行結果(超重要)
こんなの出る👇
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
👉 これをコピー
wrangler.tomlに追加
[[d1_databases]]
binding = "DB"
database_name = "saas-db"
database_id = "ここに貼る"
テーブル作成
schema.sql 作成
notepad schema.sql
👉 中身👇
CREATE TABLE users (
id TEXT PRIMARY KEY,
email TEXT UNIQUE,
plan TEXT DEFAULT 'free',
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
DBに反映
npx wrangler d1 execute saas-db --file=schema.sql
■ ③ コードを書く
ファイル開く
notepad src/index.ts
コード(コピペOK)
export default {
async fetch(request, env) {
const email = request.headers.get("cf-access-authenticated-user-email")
if (!email) {
return new Response("Unauthorized", { status: 401 })
}
let user = await env.DB.prepare(
"SELECT * FROM users WHERE email = ?"
).bind(email).first()
if (!user) {
const id = crypto.randomUUID()
await env.DB.prepare(
"INSERT INTO users (id, email, plan) VALUES (?, ?, ?)"
).bind(id, email, "free").run()
user = { id, email, plan: "free" }
}
return Response.json(user)
}
}
■ ④ ログイン(Cloudflare CLI)
npx wrangler login
👉 ブラウザ開く → ログイン
■ ⑤ デプロイ(超重要)
npx wrangler deploy
成功すると👇
https://xxxx.workers.dev
👉 これがURL
■ ⑥ ローカルテスト(任意)
npx wrangler dev
👉 表示👇
http://localhost:8787
⚠ この段階では
👉 Zero Trustないので Unauthorized出るのが正常
■ ⑦ Zero Trust接続(最後)
-
Cloudflare ダッシュボード
-
「Zero Trust」
-
Access → Applications
-
Add application
設定
-
Type → Self-hosted
-
Domain →
xxxx.workers.dev
Policy
- Include → Emails → 自分のメール
■ 動作確認
ブラウザで👇
https://xxxx.workers.dev
👉 流れ
-
ログイン画面
-
メール認証
-
JSON表示
■ よくあるエラー
❌ wrangler未インストール
npm install -g wrangler
❌ D1 binding忘れ
→ env.DB エラー出る
❌ Zero Trust未設定
→ Unauthorized
❌ email取れない
→ Access通ってない
■ ここまでで完成しているもの
👉 ✔ ログイン機能
👉 ✔ ユーザーDB
👉 ✔ API
👉 =最小SaaS
■ 次にやるべき(おすすめ)
ここまで来たら👇
-
「Stripeをコマンド付きで完全実装」
-
「Next.jsフロント接続手順」
-
「独自ドメイン化(超重要)」
Cloudflareの「Zero Trust(ゼロトラスト)」は、**一言でいうと「誰も信用しない前提のセキュリティ」**です。
■ ゼロトラストとは
通常のネットワークはこう考えます👇
-
社内ネットワーク → 信頼OK
-
外部 → 危険
でもゼロトラストは違います👇
👉 社内でも外でも全部疑う
つまり
-
ユーザー
-
デバイス
-
アクセス場所
すべて毎回チェックします。 (Cloudflare)
■ Cloudflare Zero Trustとは
Cloudflare が提供する
クラウド型セキュリティサービスで、これを簡単に実現できます。
■ 何ができるのか(超重要)
主な機能はこれ👇
① ログイン制御(Access)
-
Google / GitHub / メール認証などでログイン必須に
-
社員だけサイトに入れる
👉 例:
管理画面 / 社内ツール / SaaS
② ネット通信の監視・制御(Gateway)
-
危険サイトをブロック
-
社員の通信を管理
③ データ漏えい防止(DLP)
- 機密情報の外部送信をブロック (Cloudflare)
④ SaaSの監視(CASB)
- 勝手に使ってるツール(シャドーIT)を検知 (Cloudflare)
⑤ VPN不要アクセス
- VPNなしで安全に社内システム接続
👉 これがかなり大きいメリット
■ 従来との違い(超わかりやすく)
| 従来(VPN) | Zero Trust |
|---|---|
| 一度入れば自由 | 毎回チェック |
| 社内=安全 | 全部疑う |
| 境界防御 | 個別認証 |
■ どんな人に向いてる?
-
SaaSやAIサービスを作る人(←あなたに重要)
-
リモートワーク企業
-
社内ツールを安全公開したい人
-
Webアプリの管理画面を守りたい人
■ 超シンプルにまとめ
👉 Cloudflare Zero Trust =
「ログインしないと絶対にアクセスできないインターネット」
■ 実務での使い方(かなり重要)
例えばあなたが作るAI SaaSなら👇
-
管理画面 → Zero Trustで保護
-
API → 特定ユーザーのみ許可
-
社内ツール → 外部からでも安全アクセス
■ 一歩踏み込むと
Cloudflareはこれを
👉 Cloudflare One(SASE)
として統合提供しています。
Cloudflare Workers + Zero TrustでSaaSを構築する方法を説明します!
🏗️ 全体アーキテクチャ
ユーザー → Cloudflare Access(認証)→ Workers(アプリロジック)→ D1/KV/R2(データ)
CloudflareのスタックだけでフルスタックSaaSが完結します。
📦 使うCloudflareサービス
| サービス | 役割 |
|---|---|
| Workers | バックエンドAPI・フロントエンド配信 |
| Pages | フロントエンドホスティング(React/Next.js等) |
| Zero Trust Access | 認証・認可(ログイン) |
| D1 | SQLデータベース |
| KV | セッション・キャッシュ |
| R2 | ファイルストレージ |
| Durable Objects | リアルタイム・状態管理 |
| Queues | 非同期処理 |
🔐 認証フローの設計
パターン1:Zero Trust Access + JWT検証(おすすめ)
ユーザー → Access(IdPログイン)→ JWTトークン発行 → Workers(JWT検証)
AccessがログインさせたあとにJWTをCookieに付与してくれるので、 WorkersでそのJWTを検証するだけでOK。
// Workers側でJWTを検証する例
export default {
async fetch(request, env) {
const token = request.headers.get('Cf-Access-Jwt-Assertion');
if (!token) {
return new Response('Unauthorized', { status: 401 });
}
// JWTをCloudflareの公開鍵で検証
const identity = await verifyToken(token, env.TEAM_DOMAIN);
// identityにユーザー情報が入っている
// { email: "user@example.com", ... }
return handleRequest(request, identity, env);
}
}
async function verifyToken(token, teamDomain) {
const certsUrl = `https://${teamDomain}/cdn-cgi/access/certs`;
const certs = await fetch(certsUrl).then(r => r.json());
// JWTライブラリで検証(jose等)
}
パターン2:Workers独自認証(Access不使用)
- Workers内でセッション管理(KVにセッションを保存)
- パスワードハッシュ、OTP等を自前実装
- より柔軟だが実装コストが高い
🛠️ ステップバイステップ構築
Step 1:プロジェクト作成
# Wranglerのインストール
npm install -g wrangler
# プロジェクト作成
npm create cloudflare@latest my-saas
cd my-saas
# ログイン
wrangler login
Step 2:D1データベースのセットアップ
# D1 DBを作成
wrangler d1 create my-saas-db
wrangler.toml に追記:
[[d1_databases]]
binding = "DB"
database_name = "my-saas-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
[[kv_namespaces]]
binding = "KV"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# マイグレーション実行
wrangler d1 execute my-saas-db --file=./schema.sql
-- schema.sql
CREATE TABLE users (
id TEXT PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
plan TEXT DEFAULT 'free',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE workspaces (
id TEXT PRIMARY KEY,
owner_email TEXT NOT NULL,
name TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Step 3:WorkersでAPIを実装
// src/index.js
import { Hono } from 'hono'
import { verifyAccess } from './auth'
const app = new Hono()
// 認証ミドルウェア
app.use('*', async (c, next) => {
const identity = await verifyAccess(c.req)
if (!identity) return c.json({ error: 'Unauthorized' }, 401)
c.set('user', identity)
await next()
})
// ワークスペース一覧
app.get('/api/workspaces', async (c) => {
const user = c.get('user')
const { results } = await c.env.DB.prepare(
'SELECT * FROM workspaces WHERE owner_email = ?'
).bind(user.email).all()
return c.json(results)
})
// ワークスペース作成
app.post('/api/workspaces', async (c) => {
const user = c.get('user')
const { name } = await c.req.json()
const id = crypto.randomUUID()
await c.env.DB.prepare(
'INSERT INTO workspaces (id, owner_email, name) VALUES (?, ?, ?)'
).bind(id, user.email, name).run()
return c.json({ id, name }, 201)
})
export default app
Step 4:Zero Trust Accessの設定
- Zero Trust ダッシュボード → Access → Applications
- Add an application → Self-hosted
- 設定内容:
- Application domain:
my-saas.yourdomain.com - IdP: Google / GitHub / メール等
- Policy: 許可するユーザー・ドメインを設定
- Application domain:
Step 5:カスタムドメインとデプロイ
# デプロイ
wrangler deploy
# カスタムドメイン設定(wrangler.toml)
[env.production]
routes = [
{ pattern = "my-saas.yourdomain.com/*", zone_name = "yourdomain.com" }
]
💰 マルチテナント・プラン管理
// プランに応じた機能制限の例
app.post('/api/data', async (c) => {
const user = c.get('user')
// DBからプラン情報を取得
const { results } = await c.env.DB.prepare(
'SELECT plan FROM users WHERE email = ?'
).bind(user.email).all()
const plan = results[0]?.plan ?? 'free'
if (plan === 'free') {
// 無料プランの制限チェック
const count = await getRecordCount(user.email, c.env.DB)
if (count >= 100) {
return c.json({ error: 'Free plan limit reached' }, 403)
}
}
// 処理続行...
})
🔑 決済連携(Stripe)
WorkersからStripe APIを呼び出してサブスクリプション管理:
app.post('/api/billing/upgrade', async (c) => {
const user = c.get('user')
const session = await fetch('https://api.stripe.com/v1/checkout/sessions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${c.env.STRIPE_SECRET_KEY}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
'payment_method_types[]': 'card',
'mode': 'subscription',
'line_items[0][price]': c.env.STRIPE_PRICE_ID,
'line_items[0][quantity]': '1',
'customer_email': user.email,
'success_url': 'https://my-saas.yourdomain.com/billing/success',
'cancel_url': 'https://my-saas.yourdomain.com/billing/cancel',
})
}).then(r => r.json())
return c.json({ url: session.url })
})
✅ おすすめの技術スタック
フロントエンド: Cloudflare Pages + React / Next.js / Astro
バックエンドAPI: Cloudflare Workers + Hono
DB: Cloudflare D1(SQLite)
キャッシュ: Cloudflare KV
ファイル: Cloudflare R2
認証: Zero Trust Access(JWT)
決済: Stripe
メール: Resend / SendGrid(WorkersのFetch経由)
いい比較ポイントです。
👉 結論から言うとこうです👇
「認証まで全部任せるならSupabase」
「Cloudflareだけで完結したいならD1」
ただし、用途で最適解が変わります。実務目線で分解します👇
■ ざっくり比較
| 項目 | Supabase | Cloudflare D1 |
|---|---|---|
| 認証 | ◎(標準搭載) | ❌(自作 or Zero Trust) |
| DB | PostgreSQL | SQLite |
| スケール | 強い | 中規模まで |
| セットアップ | 超簡単 | やや技術寄り |
| Cloudflare連携 | △ | ◎ |
| SaaS向き | ◎ | ◎(設計次第) |
■ ① Supabase(王道SaaS構成)
特徴
👉 「Firebaseの進化版 + SQL」
できること
-
ログイン(メール / Google / GitHub)
-
ユーザー管理
-
DB(PostgreSQL)
-
Row Level Security(神機能)
メリット
✔ 認証が即完成
✔ ユーザー管理いらない
✔ DBが強い(SQLフル機能)
デメリット
❌ Cloudflareと分離
❌ レイテンシ少し増える
❌ 完全無料では制限あり
向いてる人
👉 普通のSaaSを最速で作りたい人
■ ② D1(Cloudflareネイティブ)
特徴
👉 「Workers専用の軽量DB」
メリット
✔ Cloudflareだけで完結
✔ 超高速(エッジ)
✔ シンプル構成
デメリット
❌ 認証なし
❌ SQLite(機能制限あり)
❌ 大規模には弱い
向いてる人
👉 軽量SaaS / AIツール / MVP
■ ③ Zero Trustとの相性(超重要)
Supabase
-
認証:Supabase
-
Zero Trust:不要 or 管理画面だけ
👉 一般ユーザー向けSaaS
D1
-
認証:Zero Trust
-
DB:D1
👉 内部ツール / 限定公開SaaS
■ 実務での最適構成(重要)
パターン①(最速)
👉 Supabase + Workers
ユーザー → Supabase Auth → Workers → DB
👉 一番ラク
パターン②(Cloudflare完全構成)
👉 D1 + Zero Trust
ユーザー → Zero Trust → Workers → D1
👉 シンプル&高速
パターン③(プロ構成)
👉 Supabase + Zero Trust + Workers
ユーザー → Supabase Auth
管理画面 → Zero Trust
API → Workers
DB → Supabase
👉 これが一番バランスいい
■ 判断基準(これで決めてOK)
あなたが今やるべきは👇
✔ D1を選ぶべき場合
-
Cloudflare縛りで作りたい
-
AIツール・社内ツール
-
ユーザー数少ない
✔ Supabaseを選ぶべき場合
-
SaaSとして一般公開する
-
ユーザー登録・ログイン必要
-
将来スケールする
■ 超重要な本質
👉 Zero Trustは「ログイン制御」
👉 Supabaseは「ユーザー管理」
👉 役割が違う
■ あなた向け結論(かなり重要)
今の流れ的に👇
👉 最初はD1 + Zero TrustでOK
(爆速で作れる)
👉 ユーザー増えたら
→ Supabaseに移行
ここまで来たらもうSaaS完成直前です。
👉 Stripe × D1 × Workersの連携は「設計さえ正しければ超シンプル」です。
■ 全体像(最重要)
ユーザー(ログイン済)
↓
Workers(API)
↓
Stripe(課金)
↓ webhook
Workers
↓
D1(反映)
👉 ポイント
「Stripeを正として、D1は状態保存だけ」
■ ① 事前準備
Stripeアカウント
👉 Stripe 作成
商品作成
Stripeダッシュボードで👇
-
Product:Pro Plan
-
Price:月額(例:¥980)
👉 price_id をメモ(超重要)
■ ② D1テーブル(課金用)
CREATE TABLE subscriptions (
id TEXT PRIMARY KEY,
user_id TEXT,
stripe_customer_id TEXT,
stripe_subscription_id TEXT,
status TEXT,
price_id TEXT,
current_period_end TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
■ ③ Checkoutセッション作成(Workers)
APIコード
export default {
async fetch(request, env) {
const email = request.headers.get("cf-access-authenticated-user-email")
const res = await fetch("https://api.stripe.com/v1/checkout/sessions", {
method: "POST",
headers: {
"Authorization": `Bearer ${env.STRIPE_SECRET_KEY}`,
"Content-Type": "application/x-www-form-urlencoded"
},
body: new URLSearchParams({
mode: "subscription",
"line_items[0][price]": env.PRICE_ID,
"line_items[0][quantity]": "1",
success_url: "https://yourdomain.com/success",
cancel_url: "https://yourdomain.com/cancel",
customer_email: email
})
})
const data = await res.json()
return new Response(JSON.stringify({ url: data.url }))
}
}
👉 フロント側👇
const res = await fetch("/create-checkout")
const { url } = await res.json()
location.href = url
■ ④ Webhook(ここがキモ)
Stripe → Workersへ通知
エンドポイント作成
https://yourdomain.com/webhook
Workersコード
export default {
async fetch(request, env) {
if (request.method !== "POST") {
return new Response("OK")
}
const body = await request.text()
const event = JSON.parse(body)
if (event.type === "checkout.session.completed") {
const session = event.data.object
await env.DB.prepare(`
INSERT INTO subscriptions (
id,
user_id,
stripe_customer_id,
stripe_subscription_id,
status,
price_id
) VALUES (?, ?, ?, ?, ?, ?)
`).bind(
crypto.randomUUID(),
session.customer_email,
session.customer,
session.subscription,
"active",
session.metadata?.price_id || ""
).run()
}
return new Response("ok")
}
}
■ ⑤ プラン反映(重要)
usersテーブル更新👇
await env.DB.prepare(`
UPDATE users SET plan = 'pro'
WHERE email = ?
`).bind(session.customer_email).run()
■ ⑥ 制限制御(SaaS化)
const user = await env.DB.prepare(
"SELECT plan FROM users WHERE email = ?"
).bind(email).first()
if (user.plan === "free") {
return new Response("Upgrade required", { status: 403 })
}
■ ここまでで完成するもの
👉 ✔ ログイン
👉 ✔ 課金
👉 ✔ プラン制御
👉 完全SaaS
■ よくあるミス(超重要)
❌ Webhook未検証
→ 本番は署名検証必須
❌ D1を正にする
→ Stripeが正解データ
❌ emailだけで紐づけ
→ 本来は customer_id 推奨
■ プロ設計にするなら
改良版(おすすめ)
user_id ←→ stripe_customer_id
👉 初回で紐づける
■ 最強構成(完成版)
-
認証 → Zero Trust
-
API → Workers
-
DB → D1
-
課金 → Stripe
👉 完全サーバーレスSaaS
■ 超まとめ
👉 Stripe連携の本質
-
Checkout作る
-
Webhook受ける
-
D1に保存
-
プラン制御
👉 これだけ
■ 次に行くと“収益化完成”
かなり重要👇
-
「解約・プラン変更対応」
-
「無料→有料の導線設計」
-
「月100万PVでも落ちない構成」
どこまでやる?