2026.09.20
이메일/비밀번호 로그인과 다르게, 구글 로그인은 브라우저가 우리 사이트 → 구글 → 우리 사이트로 왔다갔다 이동하는(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입니다. 이 둘을 헷갈리면 안 됩니다.
inote(FE)에서 구글 로그인 버튼을 눌러 끝까지 진행하면, 로그인 자체는 되는데 화면이 inote-server-5a63.onrender.com/(백엔드 자기 자신)로 넘어가면서 {"message":"Cannot GET /","error":"Not Found"} 404 화면이 뜸.
먼저 프로덕션에서 구글 버튼을 눌러 실제로 구글 로그인 화면까지 잘 가는지 확인 → 정상 (여기서 처음엔 스크린샷 좌표 클릭이 빗나가서 "아예 반응 없음"으로 오판했다가, 정확한 엘리먼트를 클릭하니 정상 진행되는 걸 재확인)
redirect_uri가 정확한 프로덕션 BE 주소로 가는지 직접 URL 파라미터를 까봄 → 정상 (https://inote-server-5a63.onrender.com/api/v1/auth/callback/google)
즉 ①~④(구글로 가는 방향)는 다 정상. → 문제는 ⑤~⑥(구글에서 돌아온 뒤) 구간이라고 좁혀짐
사용자가 실제로 로그인을 끝까지 진행해서 최종적으로 도착한 화면 스크린샷을 공유 → inote-server-5a63.onrender.com/(BE 루트)에 404
코드에서 callbackURL을 찾아봄:
authClient.signIn.social({ provider: "google", callbackURL: "/" });
→ "/"라는 상대 경로를 쓰고 있었음
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.
// Before
authClient.signIn.social({ provider: "google", callbackURL: "/" });
// After
authClient.signIn.social({
provider: "google",
callbackURL: `${window.location.origin}/`,
});
window.location.origin은 지금 이 코드가 실행되고 있는 브라우저 탭의 origin이므로: