Cheondi
개발 · 보안

소셜 로그인 권한 거부 처리

소셜 로그인에서 사용자가 권한을 거부한 경우를 오류가 아닌 명시적인 취소 흐름으로 다룬 기록입니다.

  • #oauth
  • #permission
  • #security

소셜 로그인 화면에서 사용자가 권한 제공을 거부하면 SDK 콜백은 성공 토큰 대신 오류를 반환했다. 앱은 이를 일반 로그인 실패로 보여주고 자동 재시도해 같은 권한 화면을 다시 열었다. 사용자의 선택을 장애처럼 처리한 셈이었다.

권한 거부, 사용자의 취소, 네트워크 실패, 잘못된 설정은 모두 다음 행동이 달랐다.

공급자 오류의 앱 상태 변환

SDK별 오류 문자열을 화면에서 직접 비교하지 않고 인증 계층에서 의미 있는 결과로 바꿨다.

public abstract record SocialLoginResult
{
    public record Success(string Proof) : SocialLoginResult;
    public record Cancelled : SocialLoginResult;
    public record PermissionDenied : SocialLoginResult;
    public record TemporaryFailure : SocialLoginResult;
    public record ConfigurationError : SocialLoginResult;
}

토큰과 공급자 원문 응답은 로그나 화면 상태에 남기지 않았다.

권한 거부 이후 자동 재시도 차단

사용자가 명시적으로 거부한 조건은 시간이 지나도 저절로 바뀌지 않는다. 로그인 선택 화면으로 돌아가 다른 방법을 고르거나, 필요한 권한과 사용 목적을 확인한 뒤 사용자가 다시 시작하게 했다.

일시적 네트워크 실패만 제한적인 재시도 대상으로 두고 설정 오류는 개발·운영 확인이 필요한 상태로 분리했다.

최소 권한과 기능 저하

로그인에 꼭 필요하지 않은 추가 권한은 첫 인증에서 요구하지 않고 해당 기능을 사용할 때 요청하는 방식을 검토했다. 선택 권한을 거부해도 기본 로그인은 가능하도록 공급자 계약과 서버 처리를 맞췄다.

권한 안내 문구는 왜 필요한지 설명하되 동의를 강요하거나 거부 버튼을 숨기지 않았다.

서버도 선택 권한이 비어 있는 요청을 정상적인 계약으로 처리해야 했다. 클라이언트에서만 기본값을 넣으면 다른 플랫폼과 동작이 달라질 수 있어 API 응답과 계정 연결 상태를 함께 확인했다.

사용자 거부 선택의 테스트 시나리오

개발할 때 성공 콜백만 반복하면 거부 흐름은 뒤늦게 발견된다. 첫 동의 거부, 이전 동의 철회, 일부 권한만 허용, 브라우저에서 취소, 앱 복귀를 확인했다.

이 경험 뒤 외부 인증의 오류 코드를 성공하지 못한 한 덩어리로 보지 않는다. 사용자가 선택한 결과와 시스템 실패를 구분해야 안전하고 예측 가능한 로그인 경험이 됐다.

추가 구현 기준

이 문제를 고친 뒤 관련 공식 자료를 찾아 읽으면서, 당시 코드에서 우연히 맞았던 부분과 규칙으로 남겨야 할 부분을 나눠 봤다.

OAuth 2.0은 사용자, 클라이언트, 인가 서버, 리소스 서버의 역할을 분리한다. 로그인 SDK가 토큰을 돌려줬다는 사실만으로 우리 서비스의 세션이 완성되는 것은 아니다. 서버가 issuer, audience, 만료와 필요한 범위를 확인한 뒤 내부 주체와 연결해야 한다.

클라이언트에서는 토큰 내용을 업무 권한처럼 신뢰하지 않고, 취소·거부·만료를 서로 다른 종료 상태로 다루는 편이 안전하다.

다음에 소셜 로그인 권한 거부 처리 같은 문제를 보면 아래 순서부터 확인하려고 한다.

  • 익명·인증 중·인증됨·만료 상태
  • 재시도 가능한 오류와 사용자 입력 필요 오류
  • 토큰 폐기와 화면 초기화 순서
  • 동시에 도착한 만료 응답의 단일 처리

토큰을 URL·일반 로그·오래 유지되는 로컬 저장소에 남기지 않고, 로그아웃 시 서버 세션과 기기 상태의 폐기 범위를 함께 확인해야 한다.

공식 참고 자료