Supabase 로그인 안 됨, 원인 3가지와 확인법
Supabase 로그인이 안 될 때는 대개 콘솔이 조용해요.
진짜 인증 서버에 틀린 비밀번호를 넣었더니 화면엔 로그인 성공이 떴습니다.
재현해서 나온 원인 셋과 error 문구별로 무슨 뜻인지 정리했어요.
틀린 비밀번호를 넣고 로그인 버튼을 눌렀어요. 화면에 뜬 문구는 이거였습니다.
로그인 성공!
서버는 그 요청을 400으로 거절했어요. 브라우저도 그 400을 받았고요. 그런데 화면은 성공이라고 말합니다.
Supabase 로그인이 안 된다는 말에는 서로 다른 세 가지가 섞여 있어요. 그중 둘은 빨간 줄이 안 떠서, 검색창에 넣을 문구조차 안 생깁니다. 검색해도 안 나오는 게 당연한 거예요.
정확히 말하면 로그인이 안 되는 게 아니라, 안 됐다는 사실이 화면에 안 나옵니다.
왜 catch가 안 걸릴까요
AI에게 로그인을 붙여 달라고 하면 대개 이런 꼴이 나와요.
try {
await db.auth.signInWithPassword({ email, password });
msg.textContent = "로그인 성공!";
} catch (e) {
msg.textContent = "실패: " + e.message;
}
에러 처리가 돼 있는 것처럼 보이죠. 실제로는 아니에요.
signInWithPassword는 실패해도 예외를 던지지 않아요. 결과를 객체로 돌려줍니다. 그러니 catch는 영원히 안 걸리고, 바로 아래 성공 문구가 그대로 실행돼요.
틀린 비밀번호로 눌렀을 때 실제로 돌아온 값이에요.
{ data: { user: null, session: null },
error: { name: "AuthApiError", message: "Invalid login credentials", status: 400 } }
실패한 건 error 안에 다 적혀 있어요. 코드가 그 자리를 안 볼 뿐이죠.
같은 화면에서 콘솔에 남은 건 회색 줄 하나였어요. Failed to load resource: the server responded with a status of 400 (Bad Request). 브라우저가 남긴 네트워크 기록이지 자바스크립트 에러가 아니라서, 콘솔을 열어도 무슨 일인지 안 적혀 있어요.
고치는 건 두 줄이에요.
const { error } = await db.auth.signInWithPassword({ email, password });
if (error) { msg.textContent = "실패: " + error.message; return; }
msg.textContent = "로그인 성공!";
try와 catch를 지우고 error를 봅니다. 이게 이 글에서 제일 중요한 대목이에요.
에러가 보이는 두 경우
나머지 둘은 그래도 흔적을 남겨요. 다만 남기는 자리가 서로 달라요.
하나. signIn is not a function
콘솔에 이런 게 떠 있다면 답은 간단해요.
TypeError: supabase.auth.signIn is not a function
메서드 이름이 바뀌었어요. 예전 버전에 있던 이름을 지금 버전에 대고 부른 겁니다. 두 버전을 다 설치해서 확인해 보니, signIn은 1.35.7에서는 함수고 2.112.3에서는 undefined였어요.
| 예전 이름 | 지금 이름 |
|---|---|
auth.signIn | auth.signInWithPassword |
auth.user() | auth.getUser() |
auth.session() | auth.getSession() |
여기서 헷갈리는 게 하나 있어요. signUp은 이름이 안 바뀌었습니다. 그래서 가입은 되는데 로그인만 안 되는 모양이 나와요. 튜토리얼을 따라 하다 딱 로그인에서 멈추는 사람이 이 경우예요.
한국어로 검색하면 이 문구가 잘 안 나오는데, 대부분의 예제 코드가 2022년 이전에 쓰인 탓이에요. 글이 틀린 게 아니라 그때는 맞았던 거고요.
둘. Identifier supabase has already been declared
이건 좀 다른 종류예요. 로그인만 안 되는 게 아니라 아무것도 안 됩니다.
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
<script>
const supabase = supabase.createClient(URL, KEY);
</script>
CDN으로 불러오면 supabase라는 이름이 이미 잡혀 있어요. 거기에 같은 이름으로 변수를 또 만들면 충돌합니다.
Identifier 'supabase' has already been declared
무서운 건 이게 실행 중에 나는 에러가 아니라는 점이에요. 코드를 읽는 단계에서 나요. 그래서 그 <script> 안의 코드가 한 줄도 안 돕니다. 로그인 버튼도, 목록 표시도, 그 안에 있던 전부가요.
화면은 멀쩡해 보여요. 버튼을 눌러도 아무 반응이 없을 뿐이죠.
고치는 건 이름 하나예요. 변수를 db처럼 다른 걸로 바꾸면 그대로 됩니다. 목록을 붙이던 편에서 db를 쓴 게 이 이유고요.
error 안에 뭐가 들어 있나
error를 보기로 했으면, 그 문구가 무슨 뜻인지도 알아야 하죠. 인증 서버를 띄워놓고 하나씩 시켜서 받은 값이에요.
| 시킨 것 | error.message | status |
|---|---|---|
| 틀린 비밀번호로 로그인 | Invalid login credentials | 400 |
| 없는 계정으로 로그인 | Invalid login credentials | 400 |
| 확인메일 켜둔 채 가입 직후 로그인 | Email not confirmed | 400 |
| 여섯 자 미만 비밀번호로 가입 | Password should be at least 6 characters. | 422 |
이 표에서 두 가지를 짚고 싶어요.
첫 줄과 둘째 줄이 같은 문구예요. 비밀번호를 잘못 친 것과 계정이 아예 없는 것을 문구로는 못 가릅니다. 제가 보기엔 일부러 그렇게 한 쪽이에요. 둘을 구분해서 알려주면, 아무 이메일이나 넣어보며 그 사람이 가입했는지 캐낼 수 있으니까요. 그래서 Invalid login credentials를 봤다면 비밀번호부터 의심할 게 아니라 가입이 됐는지부터 확인하는 게 빨라요.
셋째 줄이 제일 많이 걸리는 자리예요. 확인메일을 켜두면 가입 직후에는 로그인이 안 됩니다. 그런데 가입 자체는 성공한 것처럼 돌아와요.
// 확인메일이 켜진 상태에서 signUp을 부른 결과
{ error: null, data: { user: { ... }, session: null } }
error가 null이고 user 객체까지 옵니다. 없는 건 session 하나뿐이에요. 가입 다음에 곧장 로그인 화면으로 넘기는 코드를 짜뒀다면, 아무 경고 없이 로그인 안 된 상태로 들어가요.
그럼 에러 처리만 잘하면 되는 거 아닌가요
맞아요. error를 보게 짜면 셋 다 화면에 뜹니다. 제가 이 글에서 권하는 것도 그거고요.
그런데 거기서 끝내면 이상해져요. 에러 처리를 안 한 게 아니거든요. 앞의 try와 catch는 누가 봐도 에러 처리로 보이는 모양이고, AI가 준 코드가 그 모양이었어요. 코드를 못 읽는 사람에게 그건 이미 처리된 것으로 보여요.
여기서 갈리는 건 성실함이 아니라 아는 것의 양이에요. 어떤 함수가 예외를 던지고 어떤 함수가 결과로 돌려주는지는 그 라이브러리의 규약이고, 화면만 봐서는 알 길이 없어요.
그래서 저는 판정 기준을 하나 더 두는 쪽을 권해요. 성공 문구를 화면에 띄우지 말고, 진짜로 얻은 걸 띄우세요.
const { data, error } = await db.auth.signInWithPassword({ email, password });
msg.textContent = data.session ? `${data.user.email} 로그인됨` : `실패: ${error?.message}`;
“로그인 성공!”은 제가 쓴 글자예요. 서버가 준 게 아니고요. 반면 data.user.email은 서버가 세션을 내줬을 때만 채워집니다. 내가 지어낸 문구 말고 서버가 준 값을 화면에 띄우면, 실패는 저절로 드러나요.
이건 Supabase만의 얘기가 아니에요. AI가 짠 코드를 어디까지 믿을지 판단하는 법에 적었던 것과 같은 자리예요. 화면 확인은 있어야 할 게 없어진 걸 못 잡아요. 화면에 뜬 “성공”이 어디서 온 글자인지 물어야 잡힙니다.
실패를 던지지 않고 돌려줍니다
세 가지를 순서대로 보면 돼요. 콘솔에 빨간 줄이 있으면 이름이 바뀐 메서드거나 변수 이름 충돌이에요. 빨간 줄이 없는데 안 되면 error를 안 보고 있는 겁니다.
셋을 한 문장으로 줄이면 이래요. 지금 버전은 실패를 던지지 않고 돌려줍니다. 이름이 바뀐 건 검색하면 금방 나오는데, 이 규약이 바뀐 건 잘 안 적혀 있어요. 조용한 쪽이 더 오래 가는 이유가 그거고요. 띄워 놓은 사이트에서도 같은 일이 났어요. 광고 자리 45개가 3개월간 빠져 있었는데 빌드는 계속 통과했습니다.
정답이라기보다 하루 재현해 본 기록이에요. 한계는 둘입니다. 제 컴퓨터에 띄운 인증 서버로 확인한 것이라, supabase.com에 만든 진짜 프로젝트와 문구가 다를 가능성이 남아 있어요. 그리고 로그인한 다음 접근 규칙까지 걸리는 경우는 이번에 안 봤습니다.
그래도 하나는 또렷해요. 안 되는 걸 못 보고 있는 동안에는, 무엇을 검색해야 할지도 모릅니다.
여기 말고 다른 데서 막혔다면
증상만 고르면 지금 뭘 하면 되는지 짚어줘요.
참고 자료
읽으면서 떠오른 사람에게 공유해 주세요
레스덕
· 운영자현직 개발자가 AI·바이브코딩·개발자 커리어를 직접 겪고 판단한 개인 기록입니다. 공식 자료를 간략히 요약하고, 그 위에 저의 경험·판단을 덧붙입니다. 전문 자문이 아니므로, 중요한 결정 전에는 최신 원문과 전문가 상담을 함께 확인해 주세요.
최종 수정 2026.08.13 · 문의 lessduck2@gmail.com