Cloudflare Zero Trust

Page content

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設定(手動・必須)

  1. Cloudflare ダッシュボードへ

  2. 「Zero Trust」

  3. Access → Applications

  4. 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接続(最後)

  1. Cloudflare ダッシュボード

  2. 「Zero Trust」

  3. Access → Applications

  4. Add application


設定

  • Type → Self-hosted

  • Domain →

xxxx.workers.dev

Policy

  • Include → Emails → 自分のメール

■ 動作確認

ブラウザで👇

https://xxxx.workers.dev

👉 流れ

  1. ログイン画面

  2. メール認証

  3. 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の設定

  1. Zero Trust ダッシュボード → Access → Applications
  2. Add an application → Self-hosted
  3. 設定内容:
    • Application domain: my-saas.yourdomain.com
    • IdP: Google / GitHub / メール等
    • Policy: 許可するユーザー・ドメインを設定

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でも落ちない構成」

どこまでやる?