NestJS での CSRF-CSRF 実装ガイド
概要
このガイドでは、NestJS アプリケーションで CSRF 攻撃から保護するためのcsrf-csrfライブラリの実装方法について説明します。
csrf-csrf の利用背景
なぜ csrf-csrf を使用するのか
従来、Node.js アプリケーションで CSRF 保護に広く使用されていたcsurfライブラリは、非推奨(deprecated) となりました。これは主に以下の理由によるものです:
- メンテナンスの停止
- セキュリティアップデートの停止
- 新しい Node.js バージョンとの互換性問題
このため、NestJS コミュニティではcsrf-csrfライブラリが新たに推奨されています。
csrf-csrf の特徴
- Double Submit Cookieパターンを使用した CSRF 保護
- モダンなセキュリティベストプラクティスに準拠
- TypeScript フルサポート
- 軽量で高パフォーマンス
- アクティブなメンテナンス
実装方法
1. パッケージのインストール
npm install csrf-csrf
npm install -D @types/cookie-parser
必要な依存関係:
{
  "dependencies": {
    "csrf-csrf": "^4.0.3",
    "cookie-parser": "^1.4.7"
  }
}
2. main.ts での初期設定
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { ValidationPipe } from "@nestjs/common";
import * as cookieParser from "cookie-parser";
import { doubleCsrf } from "csrf-csrf";
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // その他の設定...
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      transform: true,
    })
  );
  app.enableCors({
    credentials: true,
    origin: [process.env.FRONTEND_URL || "http://localhost:3003"],
  });
  // cookie-parserの設定(csrf-csrfの前に設定する必要がある)
  app.use(cookieParser());
  // CSRF保護の設定
  const { doubleCsrfProtection } = doubleCsrf({
    getSecret: () => {
      const secret = process.env.CSRF_SECRET;
      if (!secret) {
        throw new Error("CSRF_SECRETの環境変数が設定されてません");
      }
      return secret;
    },
    getSessionIdentifier: (req) => req.ip || "anonymous", // セッションがない場合はIPアドレスを使用
    cookieName:
      process.env.NODE_ENV === "production"
        ? "__Host-psifi.x-csrf-token"
        : "csrf-token", // 開発環境では __Host- プレフィックスを使わない
    cookieOptions: {
      httpOnly: true,
      sameSite: "lax",
      secure: process.env.NODE_ENV === "production",
      path: "/",
    },
    size: 32,
    ignoredMethods: ["GET", "HEAD", "OPTIONS"],
  });
  // CSRF保護ミドルウェアを適用
  app.use(doubleCsrfProtection);
  await app.listen(process.env.PORT ?? 3000);
}
void bootstrap();
主要な設定項目の説明
- 
getSecret: CSRF トークン生成に使用する秘密鍵
- 
getSessionIdentifier: セッション識別子(IP アドレスやセッション ID を使用)
- 
cookieName: CSRF クッキーの名前
- 
cookieOptions: クッキーのセキュリティ設定
- 
size: トークンのバイト数
- 
ignoredMethods: CSRF 保護をスキップする HTTP メソッド
3. auth.controller.ts での実装
import {
  Body,
  Controller,
  Get,
  HttpCode,
  HttpStatus,
  Post,
  Req,
  Res,
} from "@nestjs/common";
import { AuthDto } from "./dto/auth.dto";
import { AuthService } from "./auth.service";
import { Csrf, Msg } from "./interfaces/auth.interface";
import { Request, Response } from "express";
// CSRF対応のRequest型を定義
interface CsrfRequest extends Request {
  csrfToken(): string;
}
@Controller("auth")
export class AuthController {
  constructor(private readonly authService: AuthService) {}
  @Get("csrf-token")
  getCsrfToken(@Req() req: CsrfRequest): Csrf {
    // csrf-csrfミドルウェアが実行された後、req.csrfToken()が利用可能になります
    const token = req.csrfToken();
    return { csrfToken: token };
  }
  @Post("signup")
  signUp(@Body() dto: AuthDto): Promise<Msg> {
    return this.authService.signUp(dto);
  }
  @HttpCode(HttpStatus.OK)
  @Post("login")
  async login(
    @Body() dto: AuthDto,
    @Res({ passthrough: true }) res: Response
  ): Promise<Msg> {
    const jwt = await this.authService.login(dto);
    res.cookie("access_token", jwt.accessToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      sameSite: process.env.NODE_ENV === "production" ? "lax" : "none",
      path: "/",
    });
    return { message: "SignIn OK" };
  }
  @HttpCode(HttpStatus.OK)
  @Post("logout")
  logout(@Req() req: Request, @Res({ passthrough: true }) res: Response): Msg {
    res.clearCookie("access_token");
    return {
      message: "SignOut OK",
    };
  }
}
4. インターフェースの定義
auth/interfaces/auth.interface.ts:
export interface Csrf {
  csrfToken: string;
}
export interface Msg {
  message: string;
}
POSTMAN での動作確認
Step 1: CSRF トークンの取得
- 
リクエスト設定 - Method: GET
- URL: http://localhost:Port/auth/csrf-token
 
- Method: 
- 
レスポンス例 
Step 2: CSRF トークンを使用した POST リクエスト
- 
リクエスト例(Sign Up) - Method: POST
- URL: http://localhost:Port/auth/signup
- Body:
{ "email": "test@example.com", "password": "testpassword123" }
 
- Method: 
- 
Cookie 設定 - POSTMAN の設定で自動的にクッキーが送信されるようにする
- または手動で Cookie ヘッダーを設定
 
Step 3: CSRF トークンなしでのテスト
CSRF トークンを送信せずに POST リクエストを送信し、403 Forbidden エラーが返されることを確認します。
セキュリティのベストプラクティス
1. 環境変数の設定
.envファイルに以下を追加:
CSRF_SECRET=your-super-secret-csrf-key-here-32-chars-minimum
NODE_ENV=development
FRONTEND_URL=http://localhost:Port
2. 本番環境での考慮事項
- 
HTTPS 必須: secure: trueでクッキーを HTTPS 限定にする
- 強力な秘密鍵: 32 文字以上のランダムな文字列を使用
- 
適切な SameSite 設定: laxまたはstrictを使用
- __Host-プレフィックス: 本番環境ではより安全なクッキー名を使用
3. フロントエンドでの実装
// CSRFトークンを取得
const getCsrfToken = async () => {
  const response = await fetch("/auth/csrf-token", {
    credentials: "include",
  });
  const data = await response.json();
  return data.csrfToken;
};
// APIリクエスト時にCSRFトークンを送信
const makeSecureRequest = async (url, data) => {
  const csrfToken = await getCsrfToken();
  return fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-csrf-token": csrfToken,
    },
    credentials: "include",
    body: JSON.stringify(data),
  });
};
トラブルシューティング
よくある問題と解決方法
- 
403 Forbidden エラー - CSRF トークンがヘッダーに正しく設定されているか確認
- クッキーが正しく送信されているか確認
- ヘッダー名がx-csrf-tokenであることを確認
 
- 
トークンが生成されない - 
cookie-parserが csrf-csrf より前に設定されているか確認
- 環境変数CSRF_SECRETが設定されているか確認
 
- 
- 
CORS 問題 - 
credentials: trueが設定されているか確認
- オリジンが正しく設定されているか確認
 
- 
まとめ
csrf-csrf ライブラリを使用することで、NestJS アプリケーションに堅牢な CSRF 保護を実装できます。重要なポイントは:
- 適切な設定でミドルウェアを構成する
- フロントエンドで CSRF トークンを正しく処理する
- 本番環境でのセキュリティ設定を適切に行う
この実装により、CSRF 攻撃からアプリケーションを効果的に保護できます。




