ANTIGRAVITY LABEN
記事一覧/アプリ開発
アプリ開発/2026-03-20上級

Antigravity エージェントでセキュアな API バックエンドを構築する — 認証・バリデーション・レート制限の実装パターン

Antigravity の AI エージェントを活用して、JWT 認証・入力バリデーション・レート制限を備えたセキュアな API バックエンドを設計・実装する上級ガイド。

Antigravity338API6セキュリティ12認証5JWTバリデーション2レート制限2バックエンド2

取り組みの背景

API バックエンドのセキュリティは、プロダクション環境において最も重要な要素の一つです。認証の不備、入力バリデーションの欠如、レート制限の未設定は、データ漏洩やサービス停止といった深刻な問題を引き起こします。

対象読者: Antigravity の基本操作を理解しており、Node.js / TypeScript での API 開発経験がある中〜上級者。

前提知識・環境構築

必要な環境

  • Antigravity 1.20 以降
  • Node.js 20 以上
  • TypeScript 5.x
  • パッケージマネージャ(npm または pnpm)

プロジェクト初期化

Antigravity のエージェントに以下のプロンプトを投げることで、プロジェクトの雛形を自動生成できます。

# Antigravity エージェントへのプロンプト例
"Express + TypeScript で API サーバーのプロジェクトを作成して。
eslint, prettier, vitest を含むこと。
src/routes, src/middleware, src/validators ディレクトリを用意して。"

エージェントが生成する典型的なディレクトリ構造は次の通りです。

project-root/
├── src/
│   ├── index.ts          # エントリーポイント
│   ├── routes/
│   │   ├── auth.ts       # 認証ルート
│   │   └── users.ts      # ユーザールート
│   ├── middleware/
│   │   ├── authenticate.ts  # JWT 認証ミドルウェア
│   │   ├── validate.ts      # バリデーションミドルウェア
│   │   └── rateLimit.ts     # レート制限ミドルウェア
│   └── validators/
│       └── schemas.ts    # Zod スキーマ定義
├── tsconfig.json
└── package.json

概念と設計思想

セキュアな API バックエンドは、以下の 3 層防御モデル で設計します。

  1. 第 1 層 — レート制限: 過剰なリクエストをゲートウェイレベルでブロック
  2. 第 2 層 — 認証・認可: JWT トークンでユーザーの身元と権限を検証
  3. 第 3 層 — 入力バリデーション: リクエストボディ・パラメータを厳密に検証

この順序が重要です。レート制限を最初に配置することで、認証処理の負荷を軽減し、ブルートフォース攻撃を防ぎます。

Antigravity エージェントにこの設計思想を AGENTS.md に記述しておくと、後続のコード生成でも一貫したセキュリティパターンが適用されます。

# AGENTS.md(セキュリティセクション)
## API セキュリティ方針
- すべてのエンドポイントにレート制限を適用する
- 認証が必要なルートには JWT ミドルウェアを必ず挟む
- リクエストボディは Zod スキーマで検証してからビジネスロジックに渡す
- エラーレスポンスには内部情報を含めない

ステップバイステップ実装

Step 1: JWT 認証ミドルウェア

まず、JWT の発行と検証を行う認証ミドルウェアを実装します。

// src/middleware/authenticate.ts
import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
 
interface TokenPayload {
  userId: string;
  role: "user" | "admin";
  iat: number;
  exp: number;
}
 
// アクセストークン検証ミドルウェア
export function authenticate(
  req: Request,
  res: Response,
  next: NextFunction
): void {
  const authHeader = req.headers.authorization;
 
  if (!authHeader?.startsWith("Bearer ")) {
    res.status(401).json({
      error: "UNAUTHORIZED",
      message: "認証トークンが必要です",
    });
    return;
  }
 
  const token = authHeader.slice(7);
 
  try {
    const payload = jwt.verify(
      token,
      process.env.JWT_SECRET!
    ) as TokenPayload;
 
    // req オブジェクトにユーザー情報を付与
    req.user = { userId: payload.userId, role: payload.role };
    next();
  } catch (err) {
    if (err instanceof jwt.TokenExpiredError) {
      res.status(401).json({
        error: "TOKEN_EXPIRED",
        message: "トークンの有効期限が切れています",
      });
      return;
    }
    res.status(401).json({
      error: "INVALID_TOKEN",
      message: "無効なトークンです",
    });
  }
}
 
// ロールベースの認可ミドルウェア
export function authorize(...allowedRoles: string[]) {
  return (req: Request, res: Response, next: NextFunction): void => {
    if (!req.user || !allowedRoles.includes(req.user.role)) {
      res.status(403).json({
        error: "FORBIDDEN",
        message: "この操作を行う権限がありません",
      });
      return;
    }
    next();
  };
}

Antigravity エージェントへのプロンプト: 「リフレッシュトークンのローテーション機能を追加して。Redis に保存するパターンで。」 と指示すると、エージェントがトークンローテーションロジックを自動生成します。

Step 2: Zod によるスキーマバリデーション

入力バリデーションには Zod を使い、型安全かつ宣言的にスキーマを定義します。

// src/validators/schemas.ts
import { z } from "zod";
 
// ユーザー登録スキーマ
export const registerSchema = z.object({
  body: z.object({
    email: z
      .string()
      .email("有効なメールアドレスを入力してください")
      .max(255),
    password: z
      .string()
      .min(8, "パスワードは8文字以上です")
      .max(128)
      .regex(
        /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
        "英大文字・英小文字・数字をそれぞれ1文字以上含めてください"
      ),
    name: z.string().min(1).max(100).trim(),
  }),
});
 
// ページネーションスキーマ(クエリパラメータ用)
export const paginationSchema = z.object({
  query: z.object({
    page: z.coerce.number().int().min(1).default(1),
    limit: z.coerce.number().int().min(1).max(100).default(20),
    sort: z.enum(["createdAt", "updatedAt", "name"]).default("createdAt"),
    order: z.enum(["asc", "desc"]).default("desc"),
  }),
});
 
// 汎用バリデーションミドルウェア
// src/middleware/validate.ts
import { Request, Response, NextFunction } from "express";
import { AnyZodObject, ZodError } from "zod";
 
export function validate(schema: AnyZodObject) {
  return async (
    req: Request,
    res: Response,
    next: NextFunction
  ): Promise<void> => {
    try {
      await schema.parseAsync({
        body: req.body,
        query: req.query,
        params: req.params,
      });
      next();
    } catch (err) {
      if (err instanceof ZodError) {
        const errors = err.errors.map((e) => ({
          field: e.path.join("."),
          message: e.message,
        }));
        res.status(400).json({
          error: "VALIDATION_ERROR",
          details: errors,
        });
        return;
      }
      next(err);
    }
  };
}

Step 3: トークンバケット方式のレート制限

IP アドレスごとにリクエスト数を制限するレート制限ミドルウェアを実装します。本番環境では Redis を使いますが、ここではインメモリ版とRedis 版の両方を示します。

// src/middleware/rateLimit.ts
import { Request, Response, NextFunction } from "express";
 
interface BucketEntry {
  tokens: number;
  lastRefill: number;
}
 
// インメモリ版トークンバケット(開発・小規模向け)
class TokenBucket {
  private buckets = new Map<string, BucketEntry>();
  private maxTokens: number;
  private refillRate: number; // トークン/秒
 
  constructor(maxTokens: number, refillRate: number) {
    this.maxTokens = maxTokens;
    this.refillRate = refillRate;
 
    // 古いエントリを定期的にクリーンアップ
    setInterval(() => this.cleanup(), 60_000);
  }
 
  consume(key: string): { allowed: boolean; remaining: number; retryAfter?: number } {
    const now = Date.now();
    let bucket = this.buckets.get(key);
 
    if (!bucket) {
      bucket = { tokens: this.maxTokens - 1, lastRefill: now };
      this.buckets.set(key, bucket);
      return { allowed: true, remaining: bucket.tokens };
    }
 
    // トークンを補充
    const elapsed = (now - bucket.lastRefill) / 1000;
    bucket.tokens = Math.min(
      this.maxTokens,
      bucket.tokens + elapsed * this.refillRate
    );
    bucket.lastRefill = now;
 
    if (bucket.tokens < 1) {
      const retryAfter = Math.ceil((1 - bucket.tokens) / this.refillRate);
      return { allowed: false, remaining: 0, retryAfter };
    }
 
    bucket.tokens -= 1;
    return { allowed: true, remaining: Math.floor(bucket.tokens) };
  }
 
  private cleanup(): void {
    const cutoff = Date.now() - 300_000; // 5 分以上前のエントリを削除
    for (const [key, entry] of this.buckets) {
      if (entry.lastRefill < cutoff) this.buckets.delete(key);
    }
  }
}
 
// エンドポイント別にバケットを作成
const generalBucket = new TokenBucket(60, 1);    // 60 req/分
const authBucket = new TokenBucket(10, 0.167);   // 10 req/分(ブルートフォース防止)
 
export function rateLimit(type: "general" | "auth" = "general") {
  const bucket = type === "auth" ? authBucket : generalBucket;
 
  return (req: Request, res: Response, next: NextFunction): void => {
    const key = req.ip || req.socket.remoteAddress || "unknown";
    const result = bucket.consume(key);
 
    res.setHeader("X-RateLimit-Remaining", result.remaining.toString());
 
    if (!result.allowed) {
      res.setHeader("Retry-After", result.retryAfter!.toString());
      res.status(429).json({
        error: "RATE_LIMITED",
        message: "リクエスト数の上限に達しました。しばらく待ってからお試しください。",
        retryAfter: result.retryAfter,
      });
      return;
    }
    next();
  };
}

Step 4: ルートへの統合

3 つのミドルウェアをルートに統合し、3 層防御を実現します。

// src/routes/users.ts
import { Router } from "express";
import { authenticate, authorize } from "../middleware/authenticate";
import { validate } from "../middleware/validate";
import { rateLimit } from "../middleware/rateLimit";
import { paginationSchema } from "../validators/schemas";
 
const router = Router();
 
// GET /users — 管理者のみ、レート制限付き
router.get(
  "/",
  rateLimit("general"),      // 第 1 層: レート制限
  authenticate,              // 第 2 層: 認証
  authorize("admin"),        // 第 2 層: 認可
  validate(paginationSchema), // 第 3 層: バリデーション
  async (req, res) => {
    const { page, limit, sort, order } = req.query as any;
    // ビジネスロジック — DB クエリ等
    res.json({ users: [], total: 0, page, limit });
  }
);
 
export default router;

応用パターン

パターン 1: エンドポイント別のレート制限戦略

認証系エンドポイント(ログイン、パスワードリセット)には厳しいレート制限を適用し、公開 API には緩めの制限を設定するのがベストプラクティスです。

// 認証ルートには厳しい制限
app.use("/api/auth", rateLimit("auth"));     // 10 req/分
// 一般ルートには通常の制限
app.use("/api/v1", rateLimit("general"));    // 60 req/分

パターン 2: Antigravity エージェントによるセキュリティ監査

Antigravity の Manager Surface を使えば、専用のセキュリティ監査エージェントを設定できます。

# AGENTS.md — セキュリティ監査エージェント
## security-auditor
あなたはセキュリティ専門のレビュアーです。以下を重点的にチェックしてください:
- SQL インジェクション・XSS の脆弱性
- 認証バイパスの可能性
- 環境変数のハードコーディング
- エラーメッセージへの内部情報の露出
- CORS 設定の適切性

パターン 3: Helmet と CORS の設定

Express のセキュリティヘッダー設定は Helmet で自動化できます。

import helmet from "helmet";
import cors from "cors";
 
app.use(helmet());
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(",") || [],
  methods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true,
  maxAge: 86400,
}));

トラブルシューティング

JWT トークンが常に無効と判定される

原因として多いのは、環境変数 JWT_SECRET が設定されていないケースです。.env ファイルと dotenv の読み込み順序を確認してください。また、トークンの発行時と検証時で異なるシークレットを使っていないかもチェックしましょう。

レート制限が効かない(常にリセットされる)

インメモリ版のトークンバケットは、サーバーの再起動でリセットされます。開発中に nodemon で頻繁にリスタートしている場合は正常な動作です。本番環境では Redis ベースの実装に切り替えてください。

Zod のバリデーションエラーが詳細すぎる

プロダクション環境では、バリデーションエラーの詳細をそのままクライアントに返すとセキュリティリスクになります。環境変数で出力レベルを制御しましょう。

const isDev = process.env.NODE_ENV === "development";
// 本番ではフィールド名のみ返し、詳細メッセージは省略
const errors = err.errors.map((e) => ({
  field: e.path.join("."),
  ...(isDev && { message: e.message }),
}));

パフォーマンス考慮事項

  • JWT の検証コスト: HS256 は軽量ですが、マイクロサービス間では RS256(公開鍵方式)を使うと、各サービスが秘密鍵を持たずに検証できます。
  • レート制限のストレージ: 小規模なら Map で十分ですが、複数プロセス / 複数サーバー構成では Redis を使ってください。Antigravity エージェントに 「Redis ベースのレート制限に切り替えて」 と指示すれば、ioredis を使った実装を自動生成できます。
  • Zod のパフォーマンス: 大量のリクエストを処理する場合、Zod のパース処理がボトルネックになることがあります。z.preprocess でコストの高い変換を避け、シンプルなスキーマを維持しましょう。

まとめ

Antigravity の AI エージェントを活用することで、セキュアな API バックエンドの構築を大幅に加速できます。本記事で紹介した 3 層防御モデル — レート制限、JWT 認証、Zod バリデーション — を組み合わせることで、プロダクションレベルのセキュリティを実現できます。

重要なのは、エージェントが生成したコードを盲信せず、セキュリティ観点でのレビューを欠かさないことです。AGENTS.md にセキュリティ方針を明記し、Manager Surface でセキュリティ監査エージェントを設定することで、開発プロセス全体のセキュリティレベルを底上げできます。

次のステップとして、Antigravity + Cloudflare Workers でエッジ API を構築するや、Antigravity + Prisma ORM でデータベース層を設計するも参考にしてください。また、Antigravity の AI コードレビュー機能を活用して、セキュリティレビューを自動化することもおすすめです。

シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

アプリ開発2026-05-04
Veo 3 APIで動画生成アプリを Antigravity で作る — 実装から公開まで
Google Veo 3 APIを使った動画生成アプリをAntigravityで実装する方法を解説。API呼び出し・UI設計・課金フローまで、個人開発者がゼロから作れる実践的なガイドです。
アプリ開発2026-03-21
Antigravity × Deno 2 開発ガイド — セキュアな次世代ランタイムで AI 駆動開発を始める
Deno 2 と Antigravity を組み合わせた開発ガイド。パーミッションシステム、npm 互換、Fresh フレームワークを AI エージェントで効率的に構築する実践手順を解説します。
アプリ開発2026-07-15
エージェントが足した依存を、インストール前に検疫する — postinstall を既定で止めて通す設計
エージェントに任せた作業で依存が増えるとき、インストールの瞬間に走るライフサイクルスクリプトは誰も見ていません。既定を止め、検疫スコアで通す実装と運用をまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →