1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【NestJS】CSRF保護の実装方法(csrf-csrf)

Last updated at Posted at 2025-07-31

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 トークンの取得

  1. リクエスト設定

    • Method: GET
    • URL: http://localhost:Port/auth/csrf-token
  2. レスポンス例

スクリーンショット 2025-07-31 22.30.13.png

  1. 重要な確認事項
    • レスポンスヘッダーにSet-Cookieが含まれていることを確認
    • クッキー名が設定通り(開発環境ではcsrf-token)であることを確認
      スクリーンショット 2025-07-31 22.32.53.png

Step 2: CSRF トークンを使用した POST リクエスト

  1. Headers 設定
    スクリーンショット 2025-07-31 22.34.05.png

  2. リクエスト例(Sign Up)

    • Method: POST
    • URL: http://localhost:Port/auth/signup
    • Body:
      {
        "email": "test@example.com",
        "password": "testpassword123"
      }
      
  3. Cookie 設定

    • POSTMAN の設定で自動的にクッキーが送信されるようにする
    • または手動で Cookie ヘッダーを設定

Step 3: CSRF トークンなしでのテスト

CSRF トークンを送信せずに POST リクエストを送信し、403 Forbidden エラーが返されることを確認します。

スクリーンショット 2025-07-31 22.35.05.png

セキュリティのベストプラクティス

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),
  });
};

トラブルシューティング

よくある問題と解決方法

  1. 403 Forbidden エラー

    • CSRF トークンがヘッダーに正しく設定されているか確認
    • クッキーが正しく送信されているか確認
    • ヘッダー名がx-csrf-tokenであることを確認
  2. トークンが生成されない

    • cookie-parserが csrf-csrf より前に設定されているか確認
    • 環境変数CSRF_SECRETが設定されているか確認
  3. CORS 問題

    • credentials: trueが設定されているか確認
    • オリジンが正しく設定されているか確認

まとめ

csrf-csrf ライブラリを使用することで、NestJS アプリケーションに堅牢な CSRF 保護を実装できます。重要なポイントは:

  • 適切な設定でミドルウェアを構成する
  • フロントエンドで CSRF トークンを正しく処理する
  • 本番環境でのセキュリティ設定を適切に行う

この実装により、CSRF 攻撃からアプリケーションを効果的に保護できます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?