인증 보안
규칙
- 비밀번호는 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에서 무제한 로그인 시도를 허용하는 것