認証セキュリティ は何をしますか?
ルール
- パスワードはbcrypt(コストファクター12以上)またはargon2idでハッシュ化する — 平文やSHA/MD5で保存しない
- ログインエンドポイントにレート制限をかける:IPごとに毎分最大5回、アカウントごとに毎時最大10回
- 指数関数的ロックアウトを実装:失敗試行後に1秒、2秒、4秒、8秒...とロック時間を延長。10回失敗で15分間ハードロック
- パスワードリセットトークン:暗号学的にランダム、1回限り有効、最大1時間で期限切れ
- 汎用的なエラーを返す:「メールアドレスまたはパスワードが無効です」 — どちらのフィールドが間違っているかを決して明かさない
- 最低8文字のパスワード長を強制し、漏洩パスワードリスト(HaveIBeenPwned API)と照合する
import bcrypt from "bcrypt";
const hash = await bcrypt.hash(password, 12);
const valid = await bcrypt.compare(password, storedHash);
if (!valid) {
await incrementFailedAttempts(userId);
return res.status(401).json({ error: "Invalid email or password" });
}
await resetFailedAttempts(userId);
async function checkLockout(userId: string) {
const attempts = await getFailedAttempts(userId);
if (attempts.count >= 10) {
const lockoutEnd = attempts.lastAttempt + 15 * 60 * 1000;
if (Date.now() < lockoutEnd) throw new Error("Account temporarily locked");
}
}
避けるべきこと
- パスワードを平文、base64、または可逆暗号化で保存すること
- パスワードにSHA-256を使用すること — 高速であるためブルートフォースが可能
- ログインやパスワードリセット時にメールアドレスの存在を明かすこと
- 期限が切れない、または再利用可能なパスワードリセットリンク
- 単一IPからの無制限のログイン試行を許可すること