배경: 로그인은 되더라도 경험이 나쁘면 심사에서 막힌다

초기 macOS Electron 구현은 Apple OAuth URL을 기본 브라우저로 열고, 커스텀 스킴 딥링크로만 앱에 복귀하는 구조였습니다. 기능적으로는 가능한 접근이었지만, 앱 밖으로 컨텍스트가 튀는 순간 사용자는 현재 앱의 일부인지 외부 사이트인지 감각을 잃기 쉬웠습니다.

App Review 관점에서도 이 흐름은 불리했습니다. 특히 계정 생성과 로그인에서 기본 브라우저를 강하게 노출하면 'poor user experience'로 평가될 수 있고, macOS 빌드에서는 Apple 로그인 오류까지 함께 재현되면서 사용성 이슈와 설정 이슈가 한 번에 드러났습니다.

초기 문제를 세 갈래로 분리했다

문제를 한 항목으로 뭉뚱그리면 해결 속도가 느려집니다. 이번 건은 코드 UX, 인증 설정, 시각 가독성으로 나눠 보는 것이 맞았습니다.

  • UX 문제: Electron에서 `globalThis.open(..., "_blank")`로 외부 브라우저를 열어 앱 흐름이 끊김
  • 설정 문제: Apple Service ID, callback URL, Supabase Apple provider `client_id` 순서가 맞지 않으면 `invalid_request`가 발생
  • 가독성 문제: 다크 시스템 테마 + 좁은 인증 창 조합에서 Apple 계정 입력 화면의 명도 대비가 낮아짐

전환 원칙: 인증 세션의 소유권을 메인 프로세스로 올린다

렌더러에서 직접 새 창을 띄우는 방식은 구현은 단순하지만 인증 상태와 창 생명주기를 앱이 통제하기 어렵습니다. 그래서 인증 창 생성과 종료, 리디렉션 가로채기, 콜백 전달 책임을 Electron 메인 프로세스로 옮겼습니다.

렌더러는 '인증 URL을 열어 달라'고 요청만 하고, 실제 BrowserWindow 생성과 `aim://auth/callback` 감지는 메인 프로세스가 담당하도록 바꾸면 UX와 안정성을 동시에 챙길 수 있습니다.

TS
// renderer
const result = await electronAPI.openAuthSession(data.url, "aim://auth/callback");
if (result.type === "success" && result.url) {
  await handleSessionFromUrl(result.url);
}
JS
// main
ipcMain.handle("auth:open-session", async (_event, { authUrl, redirectUrl }) => {
  const window = createAuthWindow();

  return await new Promise((resolve) => {
    const handleNavigation = (event, targetUrl) => {
      if (!targetUrl.startsWith(redirectUrl)) return;
      event.preventDefault();
      dispatchAuthCallback(targetUrl);
      resolve({ type: "success", url: targetUrl });
      window.close();
    };

    window.webContents.on("will-redirect", handleNavigation);
    window.webContents.on("will-navigate", handleNavigation);
    window.loadURL(authUrl);
  });
});

운영 설정에서 실제로 막힌 지점

코드를 바꾼 뒤에도 Apple 계정 화면에서 `invalid_request`가 재현됐습니다. 원인은 Supabase Apple provider 설정과 Apple Developer 설정의 정합성이 깨져 있었기 때문입니다.

macOS Electron의 OAuth 경로에서는 앱 번들 ID보다 Service ID가 더 중요합니다. `Secret Key`의 subject도 Service ID여야 하고, Supabase의 `Client IDs` 목록에서도 Service ID가 우선되어야 Apple 웹 OAuth가 올바른 `client_id`를 선택합니다.

  • Service ID: `com.jiminseong.aim.web` 생성 및 Sign in with Apple 연결
  • Return URL: `https://qtkimwyotmxktwclbwqn.supabase.co/auth/v1/callback` 정확히 등록
  • Supabase `Client IDs`: `com.jiminseong.aim.web`를 맨 앞에 배치
  • Apple secret JWT 생성 시 `sub = com.jiminseong.aim.web`로 발급

가독성 개선은 Apple 페이지를 스타일링하는 일이 아니다

Apple 로그인 화면 자체를 앱 CSS로 건드리는 것은 적절하지 않습니다. 대신 우리가 제어할 수 있는 것은 호스트 모달의 크기, 배경, 시스템 테마 힌트, 확대 비율입니다.

이번 패치에서는 인증 모달을 좁은 모바일형 팝업에서 데스크톱 읽기 폭에 맞게 넓히고, 인증 중에만 라이트 테마를 강제한 뒤 창이 닫히면 원래 테마로 복원하도록 조정했습니다. 결과적으로 텍스트 대비가 올라가고 입력 필드와 보조 문구가 더 빨리 인식됩니다.

JS
authThemeSourceBeforeOverride = nativeTheme.themeSource;
nativeTheme.themeSource = "light";

authWindow = new BrowserWindow({
  width: 680,
  height: 860,
  minWidth: 560,
  minHeight: 720,
  backgroundColor: "#ffffff",
  parent: mainWindow,
  modal: true,
});

authWindow.once("ready-to-show", () => {
  authWindow.webContents.setZoomFactor(1.05);
  authWindow.show();
});

사용자 경험 측면에서 바뀐 점

가장 큰 변화는 사용자가 '로그인하러 다른 앱으로 나갔다'는 느낌을 덜 받게 된 것입니다. 계정 선택, 이메일 입력, 2차 확인 이후 다시 AIM으로 돌아오는 흐름이 하나의 작업처럼 연결됩니다.

두 번째 변화는 실패 원인 파악이 쉬워진 점입니다. 외부 브라우저로 튀던 시기에는 콜백 손실과 설정 오류가 섞여 보였지만, 이제는 인증 창과 앱 콜백이 같은 프로세스 제어 아래 있어 문제 구간을 더 명확히 분리할 수 있습니다.

  • 앱 밖 브라우저 전환 제거
  • 로그인 성공 후 세션 복귀 경로 단순화
  • 읽기 쉬운 인증 창으로 입력 실수와 재시도 피로도 감소
  • App Review 기준에서 설명 가능한 로그인 UX 확보

검증 기준

이 패치는 코드 수정만으로 끝나지 않습니다. 운영 설정과 화면 체감까지 함께 확인해야 실제로 품질이 올라갑니다.

  • macOS Electron에서 Apple 로그인 클릭 시 기본 브라우저가 아니라 앱 내부 모달이 열리는지 확인
  • Apple 계정 인증 후 `aim://auth/callback`으로 복귀해 세션이 생성되는지 확인
  • Service ID와 Supabase secret을 갱신한 뒤 `invalid_request`가 사라졌는지 확인
  • 다크/라이트 시스템 환경 모두에서 인증 모달 텍스트 가독성이 충분한지 육안 점검