비밀번호 재설정 이메일은 제품의 마스터 키입니다. 이 메일을 읽을 수 있는 사람은 누구든 계정을 탈취할 수 있습니다. 그래서 공격자들이 노리고, 사용자들도 이 메일을 의심하는 데 익숙해졌습니다.
좋은 재설정 흐름은 공격자를 막을 만큼 안전하면서도, 실제 사용자가 몇 초 만에 믿을 수 있을 만큼 명확해야 합니다. 이 가이드에서는 누군가 “비밀번호를 잊으셨나요?” 를 클릭하는 순간부터 변경 후 확인 이메일까지 두 가지를 모두 살펴봅니다.
전체 흐름 한눈에 보기
- 사용자가 비밀번호 찾기 페이지에 이메일을 입력합니다.
- 앱은 계정이 있든 없든 항상 같은 메시지를 보여 줍니다.
- 계정이 있으면 일회용 토큰을 만들고 재설정 링크를 이메일로 보냅니다.
- 사용자가 링크를 열어 새 비밀번호를 정하면 토큰은 소진됩니다.
- 사용자의 다른 세션을 모두 로그아웃시키고 “비밀번호가 변경되었습니다” 이메일을 보냅니다.
1단계: 누가 계정을 가지고 있는지 드러내지 않기
폼이 제출되면 항상 같은 문구로 응답하세요.
ada@example.com 계정이 있다면 비밀번호 재설정 링크를 보내 드렸습니다. 링크는 30분 후 만료됩니다.
대신 “해당 이메일로 등록된 계정이 없습니다”라고 보여 주면, 주소 목록을 가진 사람 누구에게나 그중 어떤 주소가 여러분의 제품을 사용하는지 정확히 알려 주게 됩니다. 응답 시간도 비슷하게 유지하세요. 계정 조회와 이메일 발송을 백그라운드에서 처리해, 등록되지 않은 주소일 때 페이지가 눈에 띄게 빨리 응답하지 않도록 합니다.
2단계: 강력한 일회용 토큰 만들기
링크에 담긴 토큰은 길고 무작위이며 추측할 수 없어야 합니다.
import { randomBytes, createHash } from 'node:crypto';
const token = randomBytes(32).toString('base64url'); // goes in the email
const tokenHash = createHash('sha256').update(token).digest('hex'); // goes in the database
await db.passwordResets.upsert({
userId: user.id,
tokenHash,
expiresAt: new Date(Date.now() + 30 * 60 * 1000), // 30 minutes
});
규칙은 다음과 같습니다.
- 토큰의 해시만 저장하세요. 그래야 데이터베이스가 유출되어도 유효한 재설정 링크가 새어 나가지 않습니다.
- 사용자당 활성 토큰은 하나만. 새 토큰을 발급하면 이전 토큰을 대체합니다.
- 짧은 만료 시간: 보통 15분에서 60분입니다.
- 일회용. 비밀번호가 변경되는 즉시 토큰을 삭제하세요.
- 그사이 사용자가 로그인에 성공하거나 이메일 주소를 변경하면 토큰을 무효화하세요.
3단계: 즉시 이메일 보내기
사용자가 기다리고 있으므로, 배치 작업이 아니라 요청 안에서 바로 보내세요.
import os
import requests
def send_reset_email(user, token):
link = f"https://app.yourco.com/reset-password/{token}"
res = requests.post(
"https://backend.inbixio.com/api/send-email/",
headers={"X-API-Key": os.environ["INBIXIO_API_KEY"]},
json={
"to_email": user.email,
"subject": "Reset your YourApp password",
"message": render_reset_template(name=user.first_name, link=link, minutes=30),
},
timeout=10,
)
res.raise_for_status()
security@yourco.com처럼 인증된 전용 주소에서 발송하세요. Inbixio에서는 그 메일함을 별도의 이메일 서버로 연결하고, 인증 서비스에 그 서버에 고정된 API 키를 주면 재설정 이메일이 사용자가 예상하는 주소에서 발송됩니다. 나중에 그 서버를 제거하게 되면 키를 다시 고정하세요. 설정 방법은 API 키 보안을 참고하세요.
4단계: 사람들이 신뢰하는 재설정 이메일 작성하기
피싱 이메일은 다른 어떤 메시지보다 비밀번호 재설정 메일을 많이 흉내 냅니다. 여러분의 메일은 헷갈릴 여지가 없게 만드세요.
- 제목: 평이하고 구체적으로. 예: “YourApp 비밀번호 재설정”. 다급한 표현이나 느낌표는 쓰지 마세요.
- 명확한 버튼 하나, 그리고 버튼을 차단하는 메일 클라이언트를 위해 전체 링크를 텍스트로도 넣으세요.
- 만료 시간: “이 링크는 30분 후 만료됩니다.”
- 맥락: 요청이 언제 이루어졌는지, 알 수 있다면 어떤 브라우저나 도시에서 요청했는지.
- 본인이 요청하지 않았을 때 할 일: “비밀번호 재설정을 요청하지 않으셨다면 이 이메일을 무시하셔도 됩니다. 비밀번호는 변경되지 않습니다.”
- 다른 링크, 이미지, 홍보 문구는 넣지 마세요. 링크가 적을수록 진위를 확인하기 쉽습니다.
<div style="font-family:system-ui,sans-serif;max-width:520px;margin:auto;color:#111;line-height:1.5">
<p>Hi Ada,</p>
<p>We received a request to reset the password for your YourApp account.</p>
<p style="margin:28px 0">
<a href="https://app.yourco.com/reset-password/TOKEN"
style="background:#584efe;color:#fff;padding:12px 20px;border-radius:8px;text-decoration:none;font-weight:600">
Choose a new password
</a>
</p>
<p>This link expires in 30 minutes and can only be used once.</p>
<p>If you didn't ask to reset your password, you can safely ignore this email.</p>
<p style="color:#666;font-size:13px">Button not working? Paste this link into your browser:<br>
https://app.yourco.com/reset-password/TOKEN</p>
</div>
5단계: 링크를 신중하게 처리하기
사용자가 링크를 열면:
- 토큰의 해시를 조회해 만료되지 않았는지 확인하고 “새 비밀번호” 폼을 보여 주세요. 아직 사용자를 로그인시키지는 마세요.
- 토큰이 서드파티 요청으로 새어 나가지 않게 하세요. 재설정 페이지에
<meta name="referrer" content="no-referrer">를 추가하고, 그 페이지에서는 분석 도구나 광고 스크립트를 불러오지 마세요. 그래야 URL에 담긴 토큰이 리퍼러 헤더를 통해 유출되지 않습니다. - 제출 시에는 새 비밀번호가 비밀번호 규칙을 충족하는지 확인하고, 느린 알고리즘(bcrypt, scrypt, Argon2)으로 해시한 뒤, 토큰을 삭제하고 다른 모든 세션을 로그아웃시키세요.
6단계: 변경 사실 확인 메일 보내기
비밀번호가 변경되면 항상 짧은 확인 메일을 보내세요.
YourApp 비밀번호가 10월 28일 14:32 UTC에 변경되었습니다. 본인이 변경하셨다면 더 이상 하실 일은 없습니다. 본인이 아니라면 지금 바로 계정을 보호하세요.
이 이메일은 다른 사람이 계정에 들어왔을 때 사용자에게 울리는 경보이므로, 절대 생략하지 마세요.
확실히 도착하게 하기
스팸함에 들어간 재설정 이메일은 곧 고객 지원 문의로 돌아옵니다. 출시 전에:
- 발신 도메인에 SPF, DKIM, DMARC를 설정하고,
- Gmail과 Outlook 주소로 테스트해 헤더에 PASS가 표시되는지 확인하고,
- 배포 후에는 Inbixio 대시보드의 Failed Deliveries(전달 실패) 비율을 지켜보세요.
체크리스트
- 등록된 이메일과 등록되지 않은 이메일에 동일한 응답
- 32바이트 무작위 토큰, 해시만 저장
- 15–60분 만료, 일회용, 사용자당 활성 토큰 하나
- 인증된 전용 발신 주소에서 즉시 발송
- 명확한 템플릿: 버튼 하나, 일반 텍스트 링크, 만료 시간, “본인이 아니신가요?” 문구
- 재설정 페이지에 서드파티 스크립트 없음, 리퍼러 비활성화
- 변경 후 다른 세션 로그아웃 및 확인 이메일 발송