프로그램의 업무 기능이 준비돼 있어도 웹에서 연결이 멈출 수 있다. 이 맞춤 ERP의 기록을 다시 읽으며 내가 중요하게 본 것은 오류를 없앴다는 한 문장이 아니었다. 어떤 클라이언트의 어떤 반환 주소를 허용했는지, 그 뒤에도 사용자의 동의와 업무 권한이 그대로 남아 있는지를 확인하는 일이었다. 연결 문제를 고친다고 허용 범위까지 넓힐 이유는 없었다.

업무 기능의 실패와 연결 계약의 실패를 나눴다

제작 기록에서 문제의 중심은 매출 계산이나 문서 조회가 아니라 웹 연결을 시작하는 클라이언트의 반환 주소였다. 기존 구현은 로컬 앱이 사용하는 루프백 주소 형태를 검사하고 있었다. 그 계약만으로 웹 클라이언트의 정해진 반환 경로를 처리할 수 없었다. 사용자에게는 모두 연결 실패로 보이지만, 내가 수정할 위치는 업무 로직이 아니라 주소 검증의 경계였다.

이 구분이 없으면 권한이나 토큰 처리를 먼저 의심해 불필요한 변경을 만들기 쉽다. 나는 등록 단계에서 어떤 주소를 저장하는지, 인증 요청에서 그 주소를 다시 어떻게 비교하는지, 마지막에 어디로 돌아가는지를 한 흐름으로 읽었다. 오류 메시지 하나보다 입력이 지나가는 계약을 보는 편이 수정 범위를 좁히는 데 도움이 됐다.

도메인 전체 대신 검토한 주소 하나를 추가했다

실제 소스에는 정해진 웹 반환 주소를 원문 문자열로 먼저 비교하는 분기가 있다. 그 주소만 웹 경로로 받아들이고 나머지는 기존 루프백 규칙으로 검사한다. 같은 호스트 아래의 임의 경로를 허용하거나 접두어가 같으면 통과시키는 방식으로 바꾸지 않았다. 웹 연결 하나를 지원하는 변경이 다른 목적지까지 열어 주지 않도록 한 것이다.

주소를 보기 좋게 정리하는 함수도 이 경계에서는 조심해서 써야 했다. 명시한 기본 포트, 끝의 슬래시, 다른 콜백 식별자나 인코딩은 화면에서 비슷해 보여도 요청 문자열이 다르다. 이번 원본 시험은 그런 변형을 따로 거절한다. 다만 이 사례의 고정 주소를 모든 연결에 복사하면 안 된다. 실제로 설정된 클라이언트 방식과 현재 표시되는 반환 주소를 확인하는 책임은 남는다.

주소가 통과해도 업무 권한은 그대로 남는다

코드를 따라가 보면 주소 확인 다음에 클라이언트 등록의 유효성, 응답 방식, PKCE 요구, state, 리소스와 범위를 검사한다. 동의는 계정뿐 아니라 작업자와 세션에도 묶여 있다. 코드를 토큰으로 바꾸는 단계에서도 클라이언트, 반환 주소, 검증값과 리소스를 다시 확인한다. 내가 설명하려는 수정은 이 여러 확인을 건너뛰는 지름길이 아니다.

특히 웹 주소를 새로 허용했다는 이유로 조회 역할에 작성 권한을 더하면 안 된다. 원본의 역할 시험은 낮은 권한의 사용자가 더 넓은 범위를 얻지 못하는지도 확인한다. 대표가 회사 현황을 보는 화면과 직원이 처리하는 기능을 나눠 만든 프로그램이라면, 연결의 편의가 그 구분을 무너뜨리지 않아야 한다. 반환 주소와 업무 권한은 서로 다른 질문이다.

응답이 어느 발급자에게서 왔는지도 일관돼야 했다

메타데이터와 성공·거절 응답도 함께 읽었다. 구현은 발급자 식별 지원을 알리고 승인 결과와 거절 결과 모두에 iss를 돌려준다. 반환 주소만 맞아도 끝이라고 생각하면 이 연결을 놓치기 쉽다. 주소는 돌아갈 곳을, 발급자 표시는 응답의 출처를 확인하기 위한 서로 다른 부분이다.

여기서 내가 남긴 검토 항목은 단순하다. 메타데이터의 발급자 표시가 서로 일치하는지, 성공뿐 아니라 거절에서도 같은 약속을 지키는지, 그리고 이 약속을 실제 계정 연결 성공과 혼동하지 않는지다. 당시 배포 묶음과 현재 선택 소스의 바이트도 대조했다. 이후의 제품 변경까지 이번 글이 대신 보증하지는 않는다.

반례를 적어야 수정의 크기를 설명할 수 있었다

원본 시험에는 정확한 웹 주소의 성공 사례 외에도 다른 호스트, 비슷한 호스트, 사용자 정보, 다른 포트, 쿼리, 조각, 대소문자, 점 경로, 퍼센트 인코딩과 다른 콜백 식별자를 거절하는 항목이 있다. 네이티브 클라이언트가 웹 주소를 빌려 쓰지 못하는 경우와 기존 루프백 포트가 바뀌는 경우도 별도로 다룬다. 성공 하나와 거절 여러 개가 함께 있어야 변경한 경계를 읽을 수 있다.

과거 수락 기록에는 격리 시험 103개 통과가 남아 있다. 이번 제작기에서는 그 기록과 선택 소스를 읽었으며 원래 업무 프로그램을 새로 실행하지 않았다. 대신 공개용 비교 모듈을 독립적으로 작성해 현재 시험하고 압축을 푼 뒤에도 재시험했다. 과거 원제품 시험, 이번 합성 코드 시험, 웹 문서 확인은 같은 증거가 아니다.

서버 준비와 사용자의 최종 연결을 같은 말로 쓰지 않았다

수락 장부의 상태는 서버 수정과 배포 확인이 끝났지만 플러그인 설치와 실제 사용자 동의 연결은 남아 있다는 내용이다. 실제 Work 계정의 OAuth 성공도 확인되지 않은 상태로 기록돼 있다. 이 차이를 지우고 연결 완료라고 쓰면 독자는 운영에 바로 쓸 수 있다는 의미로 받아들일 수 있다. 나는 확인한 단계를 결과 문장에 그대로 남기기로 했다.

운영 화면의 문구도 같은 원칙으로 만들 수 있다. 반환 주소 확인, 서버 준비 확인, 사용자 동의 대기, 실제 계정으로 조회 확인처럼 다음 행동이 다른 상태를 구분한다. 앞의 상태가 참이어도 뒤의 상태를 자동으로 참으로 만들지 않는다. 이번에 읽은 원래 작업의 중지 상태도 바꾸지 않았으며, 제작기를 작성한다는 이유로 계정 연결을 대신 시도하지 않았다.

다음 연결에서도 재사용할 것은 주소가 아니라 확인 순서다

독자가 가져갈 것은 특정 서비스의 주소나 고객 프로그램의 원본이 아니다. 요청한 클라이언트와 대상 리소스, 신뢰할 등록 설정, 정확한 반환 값, 그 이후의 동의와 권한을 나눠 확인하는 순서다. 아래 공유 모듈은 그중 주소 비교 부분만 합성 자료로 떼어 놓았다. 다른 주소를 넣어 보고 어떤 조건이 실패하는지 직접 읽을 수 있다.

비슷한 연결 문제가 생기면 우선 마지막으로 확인된 단계를 기록하고, 그다음 경계의 입력과 반례를 적는 편이 좋다. 수정 후에도 원래 역할과 업무 범위가 남아 있는지, 공개할 수 있는 결과와 남은 사람의 조치가 무엇인지 구별한다. 작은 호환성 수정을 설명하면서도 회사가 안심하고 맡길 수 있는 제작 기준을 보여 주고 싶었다.