Designing a Frictionless Login Experience in Account Linking

문제 정의

현재 로그인 화면은 이메일 입력 → 비밀번호 입력을 고정 전제로 동작한다.
하지만 accountLinking 구조에서는 소셜로만 가입한 사용자도 존재하며, 이들은 비밀번호가 없다.
이 상태에서 비밀번호 입력을 요구하면 로그인 성공 가능성이 없는 경로로 사용자를 보내게 된다.

왜 이게 실제 문제인가

입력 UI는 단순하지만, 실제 계정 상태는 분기형이다.

  • 이메일+비밀번호 계정
  • 소셜 only 계정
  • 여러 provider가 링크된 계정 (google, reddit, kakao 등)

즉, 같은 이메일이어도 인증 경로가 다르다.
프론트가 이를 반영하지 않으면 사용자는 틀린 비밀번호를 반복 시도하고, 왜 실패하는지 이해하지 못한 채 이탈할 가능성이 커진다.

해결 목표

이번 개선의 목표는 세 가지다.

  • 소셜 only 계정을 비밀번호 단계로 보내지 않는다.
  • 사용자가 추가 선택(버튼 재클릭 등) 없이 가능한 경로로 자동 진행한다.
  • 자동 진행이 실패해도 플로우를 깨지 않고, 현재 단계에서 실패 이유를 즉시 이해하게 한다.

해결 방식: Identifier-first

핵심 아이디어는 identifier-first(이메일 우선 판별)이다.
이메일을 입력받는 즉시, 다음 단계가 비밀번호인지 소셜인지 먼저 결정한다.

text
POST /api/auth/email-sign-in-intent body: { email }

백엔드는 다음 순서로 판단한다.

  1. user + account 조회
  2. 소셜 provider가 하나 이상이면 SOCIAL_REDIRECT 반환
  3. 여러 provider가 있으면 createdAt 기준 첫 번째 provider 선택
  4. 소셜이 없으면 PASSWORD 반환

클라이언트는 응답에 따라 분기한다.

typescript
if (intent.nextAction === "SOCIAL_REDIRECT") { signIn.social(...) } else { step = "password" }

UX 원칙과 변경점

초기에는 “자동 소셜 실패 시 비밀번호 단계로 강제 이동”도 검토했지만, 이 방식은 사용자 의도와 문맥을 잃게 만든다.
최종적으로는 현재 단계 유지 + 명확한 에러 노출로 정리했다.

  • 실패 시 step=password 강제 fallback 금지
  • 이메일 단계에서 바로 에러 표시
  • 에러 위치는 이메일 필드 바로 아래
  • Continue 버튼은 요청 중 스피너 표시 + disabled 처리

Google 연동에서의 현실 제약과 대응

소셜 자동 진입 시 Google 계정 선택 화면이 다시 뜰 수 있어, “이메일을 입력했는데 왜 또 선택해야 하지?”라는 피로가 생긴다. 이를 줄이기 위해 다음을 적용했다.

  • socialProviders.google.prompt = "none"
  • 자동 소셜 시도는 disableRedirect: true로 결과를 직접 확인
  • 실패하면 /sign-in으로 복귀하고 이메일 단계 에러를 그대로 유지

최종 플로우

  1. 사용자가 이메일 입력
  2. 백엔드가 인증 경로 판별
  3. 소셜 계정이면 자동 소셜 시도
  4. 성공 시 즉시 인증 진행
  5. 실패 시 이메일 단계 유지 + 에러 표시
  6. 비밀번호 계정(또는 신규)만 비밀번호 단계 진입

이번 변경의 본질은 “입력 UI 중심”이 아니라 “계정 상태 중심”으로 로그인 흐름을 재설계한 것이다.
그 결과, 소셜 only 사용자를 잘못된 비밀번호 경로로 보내지 않고, 자동 분기와 명확한 실패 피드백을 통해 로그인 경험을 더 자연스럽고 덜 혼란스럽게 만들 수 있다.