概要

Next.jsでAPIを開発する際には、セキュリティ対策をしっかりと行うことが重要です。特に、認証やデータの保護を適切に行うことで、外部からの不正なアクセスや攻撃からアプリケーションを守ることができます。本記事では、Next.jsでセキュアなAPIを設計するための具体的な方法を紹介します。

認証と認可の実装

JSON Web Tokens (JWT)の使用

JSON Web Tokens (JWT)は、APIルートでの認証や認可に広く使われる手法です。サーバー側でトークンを生成し、クライアント側はそのトークンをリクエストに含めることでアクセスを認証します。

import jwt from 'jsonwebtoken';
const secret = process.env.API_SECRET;
export default async function handler(req, res) {
  const { username, password } = req.body;
  
  // 認証処理
  const token = jwt.sign({ username }, secret, { expiresIn: '1h' });
  res.status(200).json({ token });
}

JWTはステートレスで、トークンがクライアントに保存されるため、サーバーにユーザーセッションを保持する必要がありません。

ミドルウェアによるトークン検証

ミドルウェアを使って、APIリクエストが認証済みかどうかを検証できます。認証トークンがない場合や無効な場合は、リクエストを拒否します。

import { NextResponse } from 'next/server';
import jwt from 'jsonwebtoken';
export function middleware(req) {
  const token = req.headers.get('Authorization');
  if (!token || !jwt.verify(token, process.env.API_SECRET)) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }
  return NextResponse.next();
}

このミドルウェアにより、特定のAPIルートでのみ認証を行うことができます。

APIキーの保護

APIキーは、サーバー側でのみ扱う必要があり、クライアント側で公開してしまうとセキュリティリスクが高まります。Next.jsのサーバーコンポーネントを使用すると、APIキーをクライアントから隠しつつ、APIリクエストをサーバー側で処理できます。

const API_KEY = process.env.API_KEY;
export default async function fetchData() {
  const response = await fetch(`https://api.example.com/data?key=${API_KEY}`);
  const data = await response.json();
  
  return data;
}

この方法により、APIキーがクライアント側に漏れることを防ぎます。

レート制限とトラフィックの制御

APIへの過剰なアクセスを防ぐため、レート制限を導入することが推奨されます。express-rate-limitのようなライブラリを使うことで、一定時間内に許可されるリクエスト数を制限できます。

import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分
  max: 100, // 1つのIPにつき100リクエスト
});
export default function handler(req, res) {
  limiter(req, res, () => {
    res.status(200).json({ message: 'OK' });
  });
}

これにより、サービスへの負荷を軽減し、DoS攻撃のリスクを減らせます。

データのバリデーションとサニタイズ

クライアントから送信されるデータを検証し、SQLインジェクションやXSS攻撃のリスクを減らすために、バリデーションとサニタイズを行います。Joiexpress-validatorのようなライブラリを使用して、入力データが期待する形式かどうかを確認します。

import Joi from 'joi';
const schema = Joi.object({
  username: Joi.string().alphanum().min(3).max(30).required(),
  password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')),
});
export default async function handler(req, res) {
  const { error } = schema.validate(req.body);
  if (error) {
    return res.status(400).json({ message: error.details[0].message });
  }
  // 続行処理
}

これにより、サーバーに送られるデータが安全であることを確認できます。

まとめ

Next.jsでセキュアなAPIを設計するには、認証、ミドルウェアによる保護、APIキーの管理、レート制限、データのバリデーションとサニタイズなど、複数の対策を組み合わせることが重要です。これらの実践を通じて、APIを外部からの攻撃から守り、ユーザーのデータを安全に保つことができます。