メインコンテンツまでスキップ

第16章:サーバー側もからめる(計測/制御の“裏側”)⚙️📦

この章は「フロントだけで頑張る」から一歩進んで、サーバー側をちょい足しして “正しく測れる・守れる・育てられる” 状態にする回だよ〜📊🛡️✨ 特に AI機能って、便利なぶん 連打・コスト・悪用が起きやすいので、ここで“裏側の守り”を覚えるのがめちゃ大事🤖💸


1) まず結論:サーバー側を入れると何が嬉しい?🎁

Benefits of Server-Side

フロントの計測だけだと、こういう「あるある」に弱い😇

  • イベントが信用できない(改ざん・二重送信・Bot・通信エラー)🌀
  • 守れない(回数制限、課金事故、AI乱用、悪用)🚨
  • 秘密を置けない(APIキー、判定ロジック、セキュリティ)🔑

そこでサーバー側を入れると、こうなる👇

  • **確定イベント(=事実)**をサーバーで作れる✅
  • **ガード(制限・検知)**をサーバーで強制できる🛡️
  • 秘密をサーバーに閉じ込められる🔒

2) “サーバーちょい足し”の3パターン 🍳✨

3 Server Roles

イメージはこの3つだけ押さえればOK!

  1. 確定イベント(事実)を作る ✅📌 例:AI整形が「成功した」/「失敗した」 は、実際にサーバー処理が終わってから確定できる
  2. ガード(制限・不正対策)を強制する 🛡️⛔ 例:AIは1日N回まで怪しい連打は止める
  3. 集計・変換をサーバーへ寄せる 🧮📦 例:ログ集計重たい処理安全に整形したデータを返す

この章では 2のガードをまず作って、余力で 1の確定イベントまで触るよ💪✨


ここからは“実務の匂い”が一気に出るところ😎 ポイントは サーバーが最終判断者になること!

3-1. 仕組み(超ざっくり図)🗺️

App Check Flow

  • 画面:AIボタン押す
  • サーバー:今日の使用回数を見て OK/NG を返す
  • 画面:OKならAI実行、NGなら優しく止める🙂

さらに、Firebase App Check を使うと「その呼び出しが“本物のアプリ”から来たか」をチェックできるよ🧿✨(CallableならクライアントSDKがトークンを自動で付ける)(Firebase)


3-2. サーバー側(TypeScript)✍️⚙️

Callable関数で App Check を強制(+必要ならリプレイ対策もON)できる📌 enforceAppCheck / consumeAppCheckToken は公式の例がそのまま使えるよ。(Firebase)

// functions/src/index.ts
import { onCall, HttpsError } from "firebase-functions/v2/https";
import { initializeApp } from "firebase-admin/app";
import { getFirestore, FieldValue } from "firebase-admin/firestore";

initializeApp();
const db = getFirestore();

// JST日付キーを作る(例: 2026-02-20)
function jstDateKey(): string {
const fmt = new Intl.DateTimeFormat("ja-JP", {
timeZone: "Asia/Tokyo",
year: "numeric",
month: "2-digit",
day: "2-digit",
});
const parts = fmt.formatToParts(new Date());
const y = parts.find(p => p.type === "year")!.value;
const m = parts.find(p => p.type === "month")!.value;
const d = parts.find(p => p.type === "day")!.value;
return `${y}-${m}-${d}`;
}

export const checkAiQuota = onCall(
{
enforceAppCheck: true, // App Check必須(不正クライアント対策)
consumeAppCheckToken: true, // リプレイ保護(beta・遅延増えるので“重要APIだけ”推奨)
},
async (req) => {
if (!req.auth) {
throw new HttpsError("unauthenticated", "ログインが必要だよ🙂");
}

const uid = req.auth.uid;
const day = jstDateKey();

// “上限”はまず固定値でOK(後でRemote ConfigやDBにしてもいい)
const limitPerDay = 20;

const ref = db.doc(`aiUsage/${uid}_${day}`);

const result = await db.runTransaction(async (tx) => {
const snap = await tx.get(ref);
const used = snap.exists ? (snap.data()?.used ?? 0) : 0;

if (used >= limitPerDay) {
return { allowed: false, used, limit: limitPerDay };
}

if (!snap.exists) {
tx.set(ref, { used: 1, day, uid, updatedAt: FieldValue.serverTimestamp() });
return { allowed: true, used: 1, limit: limitPerDay };
} else {
tx.update(ref, { used: FieldValue.increment(1), updatedAt: FieldValue.serverTimestamp() });
return { allowed: true, used: used + 1, limit: limitPerDay };
}
});

return result;
}
);

💡ポイント

  • App Check強制:無効トークンは弾ける🧿(Firebase)
  • リプレイ保護:トークン使い回し攻撃に強いけど、遅くなるので“重要な関数だけ”でOK⏱️(Firebase)

3-3. フロント側(React)で呼ぶ📣🧑‍💻

WebのCallable呼び出しでは、limited-use App Checkトークンを付ける設定もできるよ(リプレイ保護をONにした場合に効く)(Firebase)

import { getFunctions, httpsCallable } from "firebase/functions";

const functions = getFunctions();

const checkAiQuota = httpsCallable(
functions,
"checkAiQuota",
{ limitedUseAppCheckTokens: true }
);

export async function canUseAi(): Promise<{allowed: boolean; used: number; limit: number;}> {
const res = await checkAiQuota();
return res.data as any;
}

UI側の流れはこんな感じ👇🙂

  • allowed ✅ → AI実行ボタンをそのまま進める
  • allowed ❌ → 「今日は上限だよ〜また明日ね🙂」を表示して止める

4) Remote Configとの“いい関係”🎛️🤝(重要な設計思想)

Soft vs Hard Limit Roles

ここ、めっちゃ大事だよ〜📌

  • Remote Config:UXのため(“先に”ボタンを灰色にする / 文言を変える)🎛️
  • サーバー側:最終防衛線(絶対に破れない上限・検知)🛡️

つまり、

  • 画面:「もうすぐ上限だよ」 と優しく案内🙂
  • サーバー:「上限超えは絶対NG」 を強制⛔

これで ユーザー体験も守りつつ、課金事故も防げる💸🧯

さらにAI寄りの話をすると、Firebase AI Logic は App Checkで保護できるし、ユーザー単位のレート制限も用意されてる(設定可能)ので、「AIは守りが必須」って思想にピタッと合うよ🤖🛡️(Firebase)


5) ハンズオン②(発展):サーバーから“確定イベント”を送る📨📊

Measurement Protocol

フロントイベントは「押した」までは分かるけど、 本当に成功したかはサーバーでしか確定できないケースがあるよね🙂

そこで、Google Analytics 4 の Measurement Protocol を使うと、サーバーからイベント送信ができる(※基本は “置き換え” じゃなく “補強” のため)📌(Google for Developers)

例:

  • ai_format_request(フロント:押した)
  • ai_format_success(サーバー:成功した)✅
  • ai_format_fail(サーバー:失敗した)❌

Measurement Protocolの実装はやや話が広がるので、ここでは「こういうことができる」まででOK!😆 (やるなら API secret などの扱いは Secrets/環境変数で管理して、Gitに置かないのが鉄則🔒)(Google Cloud Documentation)


6) “バージョン感”だけはここで固定しよう📌(詰まり防止)

Supported Runtimes

サーバー側をやるなら、ランタイムの「今どれ?」で転ばないのが大事🙂


7) AI導入済みの“時短ムーブ”🚀🤖(Antigravity / Gemini CLI)

AI Development Toolkit

ここは開発スピードが一気に上がるやつ✨

  • Google Antigravity は、エージェントを管理して「計画→実装→検証」まで進めやすい思想の開発環境だよ🛸(Google Codelabs)
  • Gemini CLI は、Firebase向けの拡張やMCP連携で、作業を自然言語で進めやすい🧠💻(Firebase)
  • Firebase MCP server は、AIツールに「Firebaseを触る手」を渡せる(プロジェクト操作や観測がしやすくなる)🧰(Firebase)
  • コンソール側は Gemini in Firebase で調査・トラブルシュートが速くなる🧯(Firebase)

Geminiに投げると強いお題(例)🪄

  • 「AI機能の不正対策チェックリストを作って」🛡️
  • checkAiQuota例外設計(unauthenticated / quota exceeded)を提案して」🧩
  • 「Analyticsイベント名を 動詞_目的語 で10個候補出して」📊

8) ミニ課題🎒✅

やることは3つだけ!

  1. checkAiQuota をCallableでデプロイする🚀

  2. Reactで呼んで、NGならボタンを止めてメッセージ表示🙂

  3. Analyticsに

    • ai_format_click(押した)
    • ai_quota_blocked(止めた) を送れるようにする📣📊

9) チェック(できたら勝ち)🏁🎉

  • App Check無しの呼び出しが弾かれる(または少なくとも「保護できてる」状態)🧿(Firebase)
  • 1日上限を超えたら必ず止まる🛡️
  • UIが優しく止めて、次の行動が分かる🙂
  • 「押した」と「止めた」がイベントで見える📊

10) よくあるミス🧯(先に潰す)

  • “UIで止めたからOK”と思う → サーバーで止めないと普通に突破される😇
  • 上限カウントをクライアントで持つ → 端末変えるとズレる・改ざんできる🌀
  • App Checkを“後でやる” → AI/課金系は特に早めが安全🧿(Firebase)
  • 秘密(API secret等)をコードに直書き → Secrets/環境変数へ🔒(Google Cloud Documentation)

次の章(第17章)は、いよいよ Performance Monitoring で「遅い」を証拠で殴れるようになる回⚡👀 この第16章の“裏側ガード”があると、改善サイクルが一気に実務っぽくなるよ〜🔁✨