Cheondi
개발 · 보안

소셜 로그인 토큰은 어디까지 믿어야 할까

소셜 로그인 토큰을 앱에서 서버로 전달하며 검증 책임과 수명을 정리한 기록입니다.

  • #oauth
  • #token
  • #security

소셜 로그인 SDK에서 토큰을 받으면 로그인 성공이라고 생각하기 쉬웠다. 앱은 공급자 화면을 거쳐 문자열 하나를 얻었지만 우리 서비스의 세션은 아직 만들어지지 않았다. 토큰이 어느 앱을 위해 발급됐는지, 만료되지 않았는지, 서버에서 검증됐는지를 확인해야 했다.

클라이언트가 토큰 내용을 읽었다고 신뢰가 생기는 건 아니었다. 최종 검증 책임을 서버에 두었다.

앱은 토큰을 전달하고 서버가 검증했다

클라이언트는 공급자 SDK에서 받은 일회성 또는 짧은 수명의 증명을 TLS 연결로 서버에 보냈다.

앱 -> 소셜 공급자 로그인
앱 <- provider token
앱 -> 서비스 서버: provider, token
서버 -> 공급자 또는 공개키로 서명·aud·iss·exp 검증
서버 -> 내부 사용자 연결 후 서비스 세션 발급

앱이 토큰 payload를 디코딩해 표시하는 것은 편의일 뿐 권한 판단에 사용하지 않았다.

로그와 저장에서 토큰을 숨겼다

디버깅을 위해 요청 객체를 통째로 출력하면 토큰이 로그에 남을 수 있었다. 공급자 종류, 요청 ID, 성공 단계, 오류 분류만 기록했다. 네트워크 도구와 예외 메시지에도 원문이 포함되지 않는지 확인했다.

장기 저장이 필요 없는 공급자 토큰은 서비스 세션 교환 뒤 메모리에서 정리했다. 로컬 설정이나 분석 이벤트에 포함하지 않았다.

재사용과 계정 연결 정책을 봤다

같은 공급자 토큰 요청이 네트워크 재시도로 두 번 도착할 수 있었다. 서버는 검증 결과와 계정 연결을 멱등하게 처리하고, 이미 사용된 일회성 코드는 다시 승인하지 않았다.

모바일 앱에서 브라우저 로그인으로 나갔다 돌아오는 redirect URI도 허용 목록과 정확히 일치해야 했다. 임의의 복귀 주소를 받아 이동하면 토큰이 다른 앱이나 페이지로 전달될 수 있었다. 요청을 시작할 때 만든 state와 nonce를 복귀 시 검증해 내가 시작한 흐름인지 확인했다.

기존 이메일 계정과 소셜 계정이 같은 주소를 쓴다고 자동 병합하면 계정 탈취 위험이 있었다. 추가 확인 절차와 제품 정책이 필요했다.

공급자 연결 해제와 우리 서비스 로그아웃도 같은 동작이 아니었다. 서비스 세션 종료, 공급자 토큰 폐기, 계정 연결 해제의 범위를 사용자 행동별로 나눴다. 탈퇴나 연결 변경처럼 영향이 큰 동작에는 재인증을 요구했다.

인증은 여러 신뢰 경계를 지나갔다

처음에는 SDK 콜백 성공과 서비스 로그인 성공을 같은 것으로 봤다. 실제로는 사용자, 소셜 공급자, 앱, 서비스 서버가 각각 다른 증거를 주고받았다.

이 작업 뒤 인증 기능을 볼 때 토큰 문자열보다 누가 발급했고 누가 무엇을 검증하는지를 그린다. 클라이언트는 편리한 UI를 제공하지만 최종 권한은 서버가 검증된 증거로 결정해야 했다.