웹 개발
웹훅 서명 검증에서 원문 본문을 먼저 읽어야 했던 이유
JSON 파싱 전 원문으로 서명을 검증하는 체크리스트입니다.
JSON이 같아도 서명 입력은 달랐다
웹훅 검증을 처음 넣을 때 본문을 JSON으로 파싱한 뒤 다시 문자열로 만들어 HMAC을 계산했다. 값은 같은데 서명이 계속 달랐다. 공백과 키 순서가 바이트를 바꾼다는 점을 놓친 것이다.
원문을 먼저 보관한다
export async function POST(request: Request) {
const raw = await request.text();
const sig = request.headers.get('x-hook-signature');
if (!sig || !verifySignature(raw, sig)) {
return Response.json({ error: 'invalid signature' }, { status: 401 });
}
const payload = JSON.parse(raw);
await enqueueEvent(payload);
return Response.json({ ok: true });
}
서명 알고리즘, 인코딩, 접두사는 공급자의 실제 샘플로 확인했다. 서명 비교는 timing-safe 함수를 사용하고 비밀 키는 로그에 남기지 않았다.
일부러 실패시킨 테스트
정상 본문, 키 순서만 바꾼 본문, 끝에 줄바꿈을 넣은 본문을 준비했다. 원문 기반 구현은 변경된 두 요청을 거절해야 한다. 이벤트 ID를 저장해 재전송에도 중복 작업이 생기지 않게 했다.
-
request.json()을 검증보다 먼저 부르지 않는다 - 오래된 timestamp를 거절한다
- 실패 응답을 2xx로 숨기지 않는다
- 검증과 JSON 형식 검사를 분리한다
웹훅 보안은 JSON이 유효한지와 보낸 주체가 맞는지를 각각 확인하는 일이다.