実践で学ぶWebセキュリティ:7つの対策で守るNext.js + FastAPIアプリケーション
初めに
Webアプリケーションを開発する際、セキュリティはユーザーデータを守るために欠かせない要素です。しかし、多くの開発者がセキュリティ対策の重要性を理解していても、実際にどのように実装すればよいのかを知る機会は限られています。
この記事では、実際に動かして学べる認証システムデモアプリケーション「next-fastapi-auth-sample」を通じて、7つの重要なWebセキュリティ対策を解説します。このアプリケーションはNext.jsとFastAPIという近代的な技術スタックで構築されており、セキュリティの基本を実践的に学ぶのに最適です。
セキュリティ対策を「体験」できるデモアプリ
このデモアプリ(next-fastapi-auth-sample)は単なる技術的な解説ではなく、実際に動かして様々なセキュリティ機能を「体験」できることが特徴です。
ユーザー登録、ログイン、保護されたダッシュボードへのアクセスなど、実際のWebアプリケーションと同様の機能を通じて、セキュリティ対策がどのように機能するのかを直接確認できます。
7つのセキュリティ対策と実際の確認方法
1. パスワード保護
実装内容
- bcryptによるパスワードのハッシュ化
- 複雑なパスワード要件の強制(大文字・小文字・数字・特殊文字を含む8文字以上)
実装コード
# security.py - パスワードハッシュ化
class SecurityManager:
@staticmethod
def hash_password(password: str) -> str:
"""
パスワードをセキュアにハッシュ化
ソルトは自動生成され、bcryptに組み込まれる
"""
return pwd_context.hash(password)
@staticmethod
def validate_password(password: str) -> bool:
"""
パスワードの複雑性チェック
- 最小8文字
- 大文字、小文字、数字、特殊文字を含む
"""
# パスワードの複雑性規則
if len(password) < 8:
return False
# 大文字、小文字、数字、特殊文字のチェック
if not re.search(r'[A-Z]', password):
return False
if not re.search(r'[a-z]', password):
return False
if not re.search(r'\d', password):
return False
if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
return False
return True
体験方法
- 「abc123」のような簡単なパスワードで登録を試みると、複雑性要件のエラーメッセージが表示されます
- 「Password123!」のような複雑なパスワードを設定すると登録が成功します
2. 安全なトークン管理
実装内容
- JWTによる安全な認証トークンの発行
- アクセストークンの有効期限設定(30分)
- トークンの署名検証
実装コード
# security.py - JWTトークン管理
class TokenManager:
@staticmethod
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
"""
アクセストークンの生成
"""
to_encode = data.copy()
# トークンの有効期限設定
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
# JWTトークンの生成
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
@staticmethod
def decode_token(token: str) -> Dict[str, Any]:
"""
トークンの検証と復号
"""
try:
# トークンをデコードし、検証
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload
except JWTError:
raise HTTPException(status_code=401, detail="トークンが無効です")
体験方法
- ログインすると、ブラウザのローカルストレージにトークンが保存されます(開発者ツールで確認可能)
- 30分後にトークンが期限切れになり、自動的にリフレッシュ処理が行われます
3. ブルートフォース攻撃対策
実装内容
- ログイン試行回数の制限(5回)
- 制限回数を超えた場合のアカウントロック
- IPアドレスベースのロックアウト
実装コード
# security.py - ブルートフォース対策
class BruteForceDefender:
"""
ブルートフォース攻撃対策
"""
_attempts: Dict[str, Dict[str, Any]] = {}
MAX_ATTEMPTS = 5
LOCKOUT_DURATION = timedelta(minutes=15)
@classmethod
def check_attempt(cls, identifier: str) -> bool:
"""
ログイン試行の検証
"""
now = datetime.utcnow()
attempt = cls._attempts.get(identifier, {
"count": 0,
"last_attempt": now
})
# ロックアウト期間中かチェック
if (attempt["count"] >= cls.MAX_ATTEMPTS and
now - attempt["last_attempt"] < cls.LOCKOUT_DURATION):
return False
# 試行回数を更新
updated_attempt = {
"count": (
1 if now - attempt["last_attempt"] > cls.LOCKOUT_DURATION
else attempt["count"] + 1
),
"last_attempt": now
}
cls._attempts[identifier] = updated_attempt
return True
体験方法
- 存在するユーザーのメールアドレスで、間違ったパスワードを5回連続で入力してみましょう
- 5回目の失敗後、アカウントが一時的にロックされ、ログイン不可になります
実際に何回か間違えてみます。
例えばこんな感じの間違いとかもあります。
5回以降間違ったらこのようにロックされます。
4. XSS(クロスサイトスクリプティング)対策
実装内容
- Content-Security-Policy(CSP)ヘッダーの設定
- X-XSS-Protection ヘッダーの設定
- X-Content-Type-Options ヘッダーの設定
- ReactによるHTMLエスケープ
実装コード
# main.py - セキュリティヘッダー設定
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
response = await call_next(request)
# XSS対策のためのセキュリティヘッダーを設定
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["Content-Security-Policy"] = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
# CSRF対策としてレスポンスが変更されないようにするヘッダー
response.headers["Cache-Control"] = "no-store, must-revalidate"
response.headers["Pragma"] = "no-cache"
# IEで自動的にMIMEスニッフィングを行わないように設定
response.headers["X-Download-Options"] = "noopen"
# クリックジャッキング対策
response.headers["X-Frame-Options"] = "DENY"
return response
体験方法
開発者ツールのネットワークタブでレスポンスヘッダーを確認すると、以下のセキュリティヘッダーが設定されています:
- X-Xss-Protection: 1; mode=block
- Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'...
- X-Content-Type-Options: nosniff
5. CSRF(クロスサイトリクエストフォージェリ)対策
実装内容
- 厳格なCORSポリシー設定
- Referrer-Policyヘッダーの設定
- SameSiteCookie設定
実装コード
# main.py - CORS設定
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 本番環境では具体的なオリジンを指定
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# main.py - セキュリティヘッダー(Referrer-Policyを含む)
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
response = await call_next(request)
# ... 他のヘッダー ...
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
# ... 他のヘッダー ...
return response
# security.py - CSRFトークン生成
def generate_csrf_token() -> str:
"""
CSRFトークンの生成
"""
return os.urandom(32).hex()
体験方法
ネットワークタブでヘッダーを確認すると、以下の設定が見られます:
- Referrer-Policy: strict-origin-when-cross-origin
- Cache-Control: no-store, must-revalidate(キャッシュ防止)
6. 安全な通信
実装内容
- 厳格なCORSポリシー設定
- HTTPSを前提とした設計
- X-Frame-Options ヘッダーの設定(クリックジャッキング対策)
実装コード
# main.py - CORS設定(前述)と追加のセキュリティヘッダー
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
response = await call_next(request)
# ... 他のヘッダー ...
# クリックジャッキング対策
response.headers["X-Frame-Options"] = "DENY"
return response
体験方法
ネットワークタブで以下のヘッダーを確認できます
- X-Frame-Options: DENY(iframeの埋め込み防止)
- Access-Control-Allow-Origin(CORS設定)
7. データベースセキュリティ
実装内容
- SQLAlchemyによるORMの使用(SQLインジェクション対策)
- パラメータ化クエリの使用
- 最小権限の原則の適用
# database.py - SQLAlchemyによるORM設定
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
# データベース接続URL (SQLite)
DATABASE_URL = "sqlite:///./auth_demo.db"
# データベースエンジンの作成
engine = create_engine(
DATABASE_URL,
connect_args={"check_same_thread": False} # SQLiteの制限を回避
)
# main.py - パラメータ化されたクエリの例
@app.post("/login", response_model=Token)
def login(
login_data: LoginRequest,
db: Session = Depends(get_db)
):
# ...
# ユーザー検索 - SQLインジェクション防止のためのパラメータ化クエリ
user = db.query(User).filter(User.email == login_data.email).first()
# ...
体験方法
コードベースでの確認が必要ですが、SQLインジェクション攻撃を試みても、パラメータ化クエリによりエスケープされるため攻撃は失敗します。
教育と学習のためのツール
このデモアプリは、Webセキュリティを学ぶ学生やエンジニア、セキュリティに興味のある方々に向けた教育ツールとして最適です。技術的な解説だけでなく、実際に手を動かして体験できることで、セキュリティ対策の重要性と仕組みをより深く理解することができます。
コードはGitHubで公開されており、各セキュリティ対策の実装方法を詳細に調査することも可能です。README.mdには技術的な説明と中学生にもわかりやすい平易な解説の両方が記載されており、様々な知識レベルの人が学べるように工夫されています。
簡単な始め方
デモアプリは以下の手順で簡単にローカル環境で実行できます:
- リポジトリをクローン:
git clone https://github.com/fumifumi0831/next-fastapi-auth-sample.git
- バックエンドのセットアップ:
cd next-fastapi-auth-sample/backend
python -m venv venv
source venv/bin/activate # Windowsの場合は venv\Scripts\activate
pip install -r requirements.txt
python main.py
- 別のターミナルでフロントエンドのセットアップ:
cd ../frontend
npm install
npm run dev
- ブラウザで http://localhost:3000 にアクセスして、アプリを体験できます
まとめ
セキュリティは知識として知るだけでなく、実際に体験することで理解が深まります。このデモアプリを通じて、最新のWeb開発技術を使った安全な認証システムの構築方法と、7つの重要なセキュリティ対策の実装と動作を学ぶことができます。
Web開発者にとっても、セキュリティ専門家にとっても、またこれからWebセキュリティを学びたい方にとっても、実践的な学習ツールとして役立つでしょう。GitHub上のリポジトリには詳細な説明が含まれており、誰でも簡単に始められる構成になっています。
ぜひ実際に動かして、モダンなWebアプリケーションに必要なセキュリティ対策を体験してみてください。
Discussion