2026.09.20

1. OAuth 소셜 로그인의 전체 흐름 (원리)

이메일/비밀번호 로그인과 다르게, 구글 로그인은 브라우저가 우리 사이트 → 구글 → 우리 사이트로 왔다갔다 이동하는(redirect 기반) 방식입니다.

① 브라우저: "구글로 로그인" 클릭
   → FE가 BE에 "나 구글로 로그인 시작할래" 요청 (POST /sign-in/social)

② BE(better-auth): 구글 로그인 URL을 만들어서 응답
   → 이 URL엔 "인증 끝나면 어디로 돌아와야 하는지"(redirect_uri)가 박혀있음
   → redirect_uri는 항상 BE 자신의 콜백 주소: https://inote-server-.../api/v1/auth/callback/google

③ 브라우저: 그 URL로 실제 이동 → 구글 로그인 화면 표시
   → 유저가 계정 선택 + 로그인

④ 구글: 인증 성공 → ②에서 지정한 redirect_uri(BE 콜백)로 브라우저를 돌려보냄
   (이때 "이 사람이 맞다"는 걸 증명하는 code를 같이 붙여서: .../callback/google?code=...)

⑤ BE: code를 받아서 구글에 "이 code가 진짜 유효한지" 재확인 → 유저 정보 획득
   → DB에 user/account/session 생성 (또는 기존 유저면 세션만 새로 생성)
   → 세션 쿠키를 Set-Cookie로 브라우저에 심음

⑥ BE: 이제 "로그인 다 끝났으니 원래 있던 앱 화면으로 돌아가라"고
   브라우저에게 또 한 번 리다이렉트 지시 (Location 헤더)
   → 이 목적지가 바로 "callbackURL" (①에서 FE가 미리 지정해뒀던 값)

핵심은 ①과 ⑥이 서로 다른 목적지라는 것: redirect_uri(②,④)는 항상 BE 자신(구글이 인증 결과를 전달할 곳)이고, callbackURL(①,⑥)은 인증이 다 끝난 뒤 유저가 최종적으로 보게 될 화면 — 즉 FE입니다. 이 둘을 헷갈리면 안 됩니다.

2. 이번에 겪은 문제

inote(FE)에서 구글 로그인 버튼을 눌러 끝까지 진행하면, 로그인 자체는 되는데 화면이 inote-server-5a63.onrender.com/(백엔드 자기 자신)로 넘어가면서 {"message":"Cannot GET /","error":"Not Found"} 404 화면이 뜸.

3. 진단 과정

  1. 먼저 프로덕션에서 구글 버튼을 눌러 실제로 구글 로그인 화면까지 잘 가는지 확인 → 정상 (여기서 처음엔 스크린샷 좌표 클릭이 빗나가서 "아예 반응 없음"으로 오판했다가, 정확한 엘리먼트를 클릭하니 정상 진행되는 걸 재확인)

  2. redirect_uri가 정확한 프로덕션 BE 주소로 가는지 직접 URL 파라미터를 까봄 → 정상 (https://inote-server-5a63.onrender.com/api/v1/auth/callback/google)

  3. 즉 ①~④(구글로 가는 방향)는 다 정상. → 문제는 ⑤~⑥(구글에서 돌아온 뒤) 구간이라고 좁혀짐

  4. 사용자가 실제로 로그인을 끝까지 진행해서 최종적으로 도착한 화면 스크린샷을 공유 → inote-server-5a63.onrender.com/(BE 루트)에 404

  5. 코드에서 callbackURL을 찾아봄:

    authClient.signIn.social({ provider: "google", callbackURL: "/" });
    

    → "/"라는 상대 경로를 쓰고 있었음

4. 원인 — 상대 경로 리다이렉트는 "응답을 보낸 쪽" 기준

HTTP 표준상, 서버가 Location: / 같은 상대 경로로 리다이렉트를 지시하면, 브라우저는 이걸 그 응답을 보낸 서버(origin) 기준으로 해석합니다. 위 흐름의 ⑥번 단계는 inote-server(BE)가 보내는 응답이기 때문에:

Location: /
→ 브라우저 해석: <https://inote-server-5a63.onrender.com/api/v1/auth/callback/google> 기준
→ 최종: <https://inote-server-5a63.onrender.com/>

FE(inote-main.vercel.app)로 가야 하는데, BE 자신의 루트로 가버린 것. BE엔 / 경로에 매핑된 라우트가 없으니(전부 /api/v1/... 밑에 있음) 404.

5. 해결

// Before
authClient.signIn.social({ provider: "google", callbackURL: "/" });

// After
authClient.signIn.social({
  provider: "google",
  callbackURL: `${window.location.origin}/`,
});

window.location.origin은 지금 이 코드가 실행되고 있는 브라우저 탭의 origin이므로: