코드 인증 API가 HTTP 200과 { "status": "REJECT" }를 반환한다고 하자. 뒤이어 인증 코드 쿠키가 사라졌다. 쿠키를 복원하면 성공할까, 아니면 업무 거절 뒤의 정리였을까.
200은 프로토콜의 성공 의미이고 REJECT는 이 API의 업무 계약이다. 쿠키는 다음 요청에 쓰일 브라우저 상태다. 세 결과를 나누면 마지막 화면 증상보다 앞의 실패 지점을 찾을 수 있다.
업무 결과를 읽는 위치를 확인한다
업무 거절을 200과 상태 필드로 표현하는 계약이라면 response.ok만으로 승인 처리할 수 없다.
const response = await fetch("/api/affiliation/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code }),
});
if (!response.ok) throw new Error("verification request failed");
const result = await response.json();
if (result.status === "APPROVAL") {
refreshMembership();
} else {
showVerificationRejected(result.reasonCode);
}
이 분기는 해당 응답 형식이 유효하다는 전제다. 실제 클라이언트에서는 필드·허용 상태를 검증하고 알 수 없는 응답은 승인으로 처리하지 않는다. 화면 갱신과 서버가 혜택 권한을 확인하는 검사도 별개다.
RFC 9110은 HTTP 상태의 의미를 정의한다. 소속 승인 조건은 애플리케이션 계약으로 정의해야 한다. 신규 API의 오류 모델을 개선하더라도 기존 클라이언트가 있는 상태에서 상태 코드만 바꾸면 호환성이 달라진다.
발급자 상태와 발급된 코드의 정책을 나눈다
관리자가 발급한 코드가 유효기간 안에 있는데 관리자는 이후 비활성화됐다고 하자. 기존 코드 조회가 활성 관리자와의 조인에만 의존하면 두 정책이 결합된다.
신규 발급: 현재 발급자 권한 + 대상 소속 + 발급 정책
기존 사용: 발급 코드의 만료 + 할당 상태 + 사용 정책
기존 코드가 계속 유효해야 하는지는 업무 판단이다. 비활성 발급자 조건을 무조건 제거하기보다 신규 발급은 금지하면서 기존 사용을 허용하는지, 기존 것도 회수하는지 결정한다. 대상·만료·재사용 조건은 해당 정책대로 유지한다.
이 사례는 조회 경계를 설명하기 위한 조건이다. 발급자 비활성화가 모든 인증 거절의 원인이라고 단정하지 않는다.
쿠키 변화 전후의 요청을 연결한다
먼저 요청에 코드가 실렸는지 확인한다. 다음으로 응답의 Set-Cookie, 클라이언트 삭제 로직, 쿠키 path·domain을 시간순으로 비교한다. RFC 6265는 쿠키 저장·교체와 범위를 설명한다.
코드를 보낸 뒤 거절 응답을 받고 삭제했으면 후속 처리 가설을 검토한다. 요청 전에 코드가 빠졌다면 전송 경로를 조사한다. 쿠키 존재 여부를 서버 인가의 원천으로 삼지는 않는다.
그림의 점선은 쿠키 삭제 원인이 확정됐다는 뜻이 아니다. 요청·응답·클라이언트 동작을 연결해 인과를 확인할 지점이다.
관측도 HTTP 오류율과 업무 거절률을 나눈다. 거절 이유를 만료·대상·상태 등 필요한 범위로 분류하고 인증·인가 실패를 과도한 계정 정보와 함께 공개하지 않는다.
검증은 활성·비활성 발급자, 만료·기처리 코드, 다른 대상 소속을 조합한다. 기존 코드 정책 변경이 신규 발급 권한까지 완화하지 않는지도 검사한다. 다음에는 쿠키를 되돌리는 조치보다 서버의 승인 판정과 실제 코드 입력이 일치하는지 먼저 확인한다.
