본문으로 건너뛰기

본인인증이 가끔 실패하던 이유, 쿼리스트링의 '+'

· 약 2분
Seung hyo, Lee
Front End Engineer @ Assistfit

CRM 로그인과 계약서 서명에는 휴대폰 본인인증이 들어간다. QA에서 본인인증이 가끔 실패한다는 제보가 이어졌는데 재현이 잘 되지 않았다.

지난주에는 증상부터 막았다. 인증이 실패하면 로그아웃시키고 로그인 화면으로 보내도록 했다. 그런데 실패 원인은 그대로였고, 실패 화면 대신 앱 전체 에러 화면(global-error)이 뜨는 경우까지 있었다.

보이게 만들기​

먼저 verify 경로에 세그먼트 error.tsx를 추가했다. 결과 페이지는 서버 컴포넌트에서 인증 결과 API를 부르는데, 여기서 throw가 나면 global-error까지 올라갔다. 세그먼트에서 받아 안내 화면을 보여주고, 에러는 콘솔과 Sentry로 보냈다.

다음으로 실패 화면에 인증 결과값(priinfo)과 요청 번호를 QA와 로컬 호스트에서만 보이게 했다. 인증 화면이 iframe 안이라 개발자 도구로 값을 보기 불편했기 때문이다. QA가 실패한 값을 그대로 복사해 줄 수 있게 됐다.

원인​

QA가 넘겨 준 값으로 원인을 좁혔다. 인증 제공사는 결과를 Base64 문자열로 만들어 returnUrl의 쿼리스트링에 붙여 보낸다. Base64에는 +가 들어갈 수 있는데, 쿼리스트링을 디코딩할 때 +는 공백으로 해석된다. 공백이 섞인 값을 백엔드가 파싱하지 못해 FAIL이 났다. 결과값에 +가 들어갈 때만 실패하니 간헐적일 수밖에 없었다.

유효한 Base64에는 공백이 없으니 공백을 다시 +로 돌려 원본을 복원했다.

// 쿼리스트링 디코딩에서 Base64의 '+'가 공백으로 바뀐 채 들어온다.
// 유효한 Base64에는 공백이 없으므로 '+'로 되돌려 원본을 복원한다.
const restoredPriinfo = priinfo.replace(/ /g, '+');

처음엔 결과 페이지에서 바로 고쳤다가, 계약서 서명 쪽도 같은 API를 쓴다는 걸 보고 복원 로직을 결과 API 함수 한 곳으로 옮겼다. 원인을 찾으려고 넣었던 디버그 노출과 진단 로그는 같은 날 모두 지웠다.