로그인은 되는데 페이지를 새로고침하거나 잠시 뒤 다시 로그인 화면으로 튕기는 증상. 바이브 코딩으로 만든 앱에서 가장 흔한 인증 버그입니다. 원인은 대부분 아래 5가지 중 하나예요. 내 증상에 맞는 항목으로 바로 가세요.
원인 빠르게 짚기
- A. 새로고침할 때마다 튕김 → 앱이 "로그인 됐나?"를 인증 라이브러리가 세션을 복원하기 *전에* 검사함 (가장 흔함)
- B. 로그인하고 몇십 분~1시간 뒤 풀림 → 토큰 자동 갱신이 꺼져 있음
- C. 아예 저장이 안 됨(시크릿 모드/쿠키 차단) → 세션 저장소(localStorage·쿠키)가 막혀 있음
- D. 배포(Next.js 등)한 뒤부터 로그아웃 → 서버 렌더링용 미들웨어가 없음
- E. 프론트/백엔드 도메인이 다름 → 쿠키 SameSite/Secure 설정 문제
A. 새로고침하면 튕김 — "세션 복원 대기"를 안 함 (가장 흔함)
Supabase도 Firebase도 저장된 로그인 정보를 불러오는 데 아주 짧은 시간이 걸립니다(비동기). 그런데 AI가 만든 코드가 화면을 켜자마자 유저 없으면 로그인 페이지로를 실행하면, 복원이 끝나기 직전의 "잠깐 비어 있는 상태"를 로그아웃으로 오해해서 매번 튕겨요.
핵심: "아직 확인 중" 상태를 따로 두고, 그동안은 로딩 화면만 보여줄 것.
Firebase (v9 모듈러):
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
// 로그인 상태 — 화면 보여주기
} else {
// 진짜 로그아웃일 때만 로그인 페이지로
}
});
onAuthStateChanged는 저장된 세션을 복원한 뒤에 호출됩니다. 판단은 이 콜백 안에서 하세요. 마운트하자마자 auth.currentUser를 보고 판단하면 안 됩니다(그 시점엔 아직 null일 수 있음).
Supabase:
// 처음 한 번: 저장된 세션 읽기
const { data: { session } } = await supabase.auth.getSession();
// 이후 변화 구독
supabase.auth.onAuthStateChange((event, session) => {
// session 이 있으면 로그인, 없으면 로그아웃
});
React라면 loading 상태를 하나 만들고, 위 콜백이 처음 도착할 때까지 loading = true로 두세요. loading 중에는 리다이렉트하지 말고 로딩 표시만 하면 됩니다.
AI에게 이렇게 요청하면 됩니다:
> "로그인 여부를 확인하는 동안 loading 상태를 두고, onAuthStateChanged(또는 onAuthStateChange) 콜백이 처음 실행되기 전에는 로그인 페이지로 리다이렉트하지 않게 고쳐줘."
B. 몇십 분 뒤 풀림 — 토큰 자동 갱신 확인
로그인 토큰(액세스 토큰)은 보통 1시간이면 만료됩니다. 그때 새 토큰으로 자동 갱신되지 않으면 "가만히 있었는데 로그아웃"이 돼요.
Supabase 클라이언트를 만들 때 아래 옵션이 켜져 있어야 합니다(브라우저에선 기본값이 켜짐 — 껐다면 다시 켜세요):
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
auth: {
persistSession: true, // 세션을 localStorage에 저장
autoRefreshToken: true, // 만료 전 토큰 자동 갱신
detectSessionInUrl: true, // OAuth 로그인 후 URL에서 세션 감지
},
})
persistSession은 세션을 localStorage에 저장하고, autoRefreshToken은 만료 전에 토큰을 자동 갱신합니다. 커스텀 auth 설정을 넣으면서 실수로 이걸 false로 둔 경우가 흔해요.
Firebase는 기본이 local 지속성이라 브라우저를 닫아도 유지되고, 토큰 갱신도 SDK가 자동으로 합니다.
C. 아예 저장이 안 됨 — 시크릿 모드 / 쿠키 차단
세션은 localStorage(또는 쿠키)에 저장됩니다. 이 저장소가 막히면 새로고침은커녕 로그인 직후에도 풀려요.
- 시크릿/프라이빗 모드, 쿠키 차단, 써드파티 쿠키/데이터 차단 상태인지 확인하세요. Firebase의
local지속성은 "브라우저가 저장을 지원할 때(예: 써드파티 쿠키/데이터 허용)"만 동작합니다. - Safari는 추적 방지(ITP) 때문에 크로스도메인 저장이 제한될 수 있어요.
- Firebase에서 명시적으로 유지하려면 로그인 전에 지속성을 지정하세요:
import { getAuth, setPersistence, browserLocalPersistence } from "firebase/auth";
const auth = getAuth();
await setPersistence(auth, browserLocalPersistence); // 그다음 signIn 호출
browserSessionPersistence(탭 닫으면 사라짐)나 inMemoryPersistence(새로고침하면 사라짐)로 설정돼 있으면 당연히 로그아웃됩니다. browserLocalPersistence로 바꾸세요.
D. Next.js 등 배포 후 로그아웃 — 서버용 미들웨어 없음
Supabase를 Next.js(App Router)에서 서버 렌더링과 함께 쓰면, 세션을 localStorage가 아니라 쿠키에 저장해야 합니다. 그런데 Server Component는 쿠키를 못 씁니다. 그래서 미들웨어에서 토큰을 갱신해 주지 않으면, 만료된 토큰이 그대로 남아 새로고침 때 로그아웃돼요.
해결:
@supabase/ssr패키지를 쓰고, 브라우저용은createBrowserClient, 서버용은createServerClient로 만드세요.- 프로젝트 루트에
middleware.ts를 두고 매 요청마다 세션을 갱신하세요(공식 문서의updateSession패턴). - 서버에서 로그인 여부를 검사할 땐
getSession()을 믿지 말고getClaims()(또는getUser())를 쓰세요.getSession()은 토큰 재검증을 보장하지 않습니다.
정확한 미들웨어 코드는 아래 sources의 Supabase 공식 SSR 문서를 그대로 복사해 쓰는 게 안전합니다.
AI에게:
> "@supabase/ssr로 createBrowserClient/createServerClient를 나눠 쓰고, middleware.ts에서 updateSession으로 매 요청 세션을 갱신하도록 만들어줘. 서버 인증 확인은 getUser로."
E. 프론트/백엔드 도메인이 다름 — 쿠키 SameSite/Secure
직접 만든 백엔드(Express 등)에서 쿠키로 로그인을 유지하는데 프론트엔드와 API 도메인이 다르면, 쿠키가 브라우저에 저장·전송되지 않아 로그아웃됩니다.
- 같은 도메인이면:
SameSite=Lax(지정 안 하면 기본이 Lax). 대부분 여기서 해결됩니다. - 다른 도메인(크로스사이트)이면:
SameSite=None; Secure를 반드시 함께 줘야 합니다.SameSite=None은Secure(HTTPS) 없이는 브라우저가 거부해요. - 그래서 localhost에서 http로 테스트하면
Secure쿠키가 안 붙어 로그인 유지가 안 될 수 있습니다. 로컬은 같은 오리진으로 프록시하거나 https로 띄우세요.
쿠키 예시:
Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/
(다른 도메인이면 SameSite=None; Secure)
SameSite=Strict로 돼 있으면 OAuth 제공자에서 돌아오는 등 다른 사이트를 거친 이동에서 쿠키가 안 실려 로그인 화면으로 튕길 수 있어요. 대부분은 Lax가 정답입니다.
그래도 안 되면 — 확정 원인 못 찾을 때
이 증상은 스택·설정마다 원인이 달라서 "만능 한 줄 수정"은 없습니다. 순서대로 좁히세요.
- 브라우저 개발자도구 → Application → Local Storage / Cookies에서 로그인 후 세션 값이 실제로 저장되는지 확인. 저장이 안 되면 → C(저장소 차단) 또는 E(쿠키 설정).
- 저장은 되는데 새로고침 시 사라지면 → A(복원 대기) 또는 D(SSR 미들웨어).
- 한동안 있다가만 풀리면 → B(토큰 갱신).
- 여기까지도 안 되면, 아래 sources의 공식 문서 예제 코드를 그대로 붙여넣어 최소 재현부터 맞추세요.
