론리
나만의 재밌는 방식으로 사는 개발자이자 투자자.
지난주에 이메일 매직링크 로그인을 붙이면서 하마터면 크게 당할 뻔한 게 있어서 공유합니다.
메일로 보낸 로그인 링크는 사용자보다 먼저 열립니다.
매직링크를 만들 때 제일 자연스러운 설계는 이겁니다.
GET /auth/verify?token=xxx → 검증하고 토큰 소진 → 세션 발급
그런데 기업 메일 게이트웨이나 보안 스캐너가 메일 안의 링크를 미리 열어봅니다. 피싱인지 확인하려고요. 그러면 1회용 토큰이 그 시점에 타버리고, 정작 사용자가 누르면 "만료된 링크"가 뜹니다.
재현이 잘 안 되는 게 고약합니다. 내 지메일에선 멀쩡한데 특정 사용자만 계속 실패합니다.
해결은 열람과 소진을 분리하는 겁니다.
- 메일 링크가 API가 아니라 앱 주소를 가리키게 합니다 (
/?login_token=xxx) - 랜딩은 토큰을 화면에 띄우기만 하고 아무것도 소비하지 않습니다
- 사용자가 "로그인하기" 버튼을 눌러야
POST /auth/email/verify로 소진합니다
스캐너는 GET만 하기 때문에 토큰이 살아남습니다. POST는 사람만 만듭니다.
그 김에 같이 넣은 것들입니다.
- 토큰은 원문을 저장하지 않고 sha256 해시만 넣습니다. DB가 털리면 평문 토큰은 그 자체로 남의 계정 열쇠입니다
- 링크 요청 엔드포인트는 가입 여부·쿨다운·발송 실패와 상관없이 항상 200입니다. 응답이 갈리면 그게 곧 "이 이메일이 가입돼 있다"는 신호가 됩니다
- 검증 실패는 만료·재사용·위조를 구분하지 않고 401로 통일했습니다. 사유를 알려주면 토큰 추측에 단서를 줍니다
- 재발급하면 이전 미사용 토큰을 무효화하고 60초 쿨다운을 뒀습니다. 남한테 메일 폭탄 보내는 데 쓰이면 안 되니까요
- 검증이 끝나면 성공·실패 상관없이 주소창에서 토큰을 지웁니다. 안 그러면 사용자가 URL을 공유할 때 토큰이 같이 새고 브라우저 기록에도 남습니다
무상태 서명 토큰(JWT류)으로 하면 테이블을 안 만들어도 되는데, 1회용을 보장할 수 없어서 접었습니다. 만료 전까지 같은 링크로 몇 번이든 로그인되고 개별 폐기도 안 됩니다. 로그인 토큰은 계정 접근 권한 그 자체라 그 통제를 포기하기가 어렵더군요.
혹시 매직링크 붙이실 계획 있으시면 GET 하나로 끝내지 마세요. 저는 운 좋게 만들면서 알았는데, 배포하고 나서 알았으면 "왜 특정 사용자만 로그인이 안 되지"로 한참 헤맸을 겁니다.
파이낸스트랙 포트폴리오
보유 종목만 등록하면 매 평일 저녁 자산 현황과 AI 리포트를 메일·텔레그램으로 보내주는 무료 도구. 증권사 앱 화면을 캡처해 올리면 종목이 자동 등록됩니다.
댓글
로그인 후 댓글을 남길 수 있습니다.
아직 댓글이 없습니다.