바이브코딩 Supabase 로그인 붙이기: 회원가입, 구글, 매직링크
이 글의 독자바이브코딩으로 화면은 만들었는데 로그인을 어떤 방식으로 붙일지 못 정한 사람
한줄 요약: AI에게 서비스를 만들어 달라고 하면 로그인 화면이 딸려 오는데, 그 화면을 실제로 돌아가게 만드는 건 다른 일입니다. 바이브코딩으로 만든 서비스에 로그인을 붙이는 길은 크게 셋입니다. 이메일과 비밀번호를 받는 영구 회원가입, 구글 계정을 빌리는 소셜 로그인, 비밀번호 없이 메일 링크로 들어오는 매직링크입니다. Supabase를 쓰면 셋 다 함수 한두 줄이지만 막히는 자리는 전부 다릅니다. 어느 것을 고를지 정하는 플로차트부터 시작해서, 각각의 구현과 장단점, 그리고 셋 중 둘이 공통으로 부딪히는 메일 발송 한도까지 정리했습니다.
목차
- 세 방법 중 무엇을 고르나
- 영구 회원가입: 이메일과 비밀번호
- 구글 로그인: 주소를 두 군데 등록한다
- 매직링크: 비밀번호를 아예 없앤다
- Resend로 메일 발송 한도를 푼다
- 로그인이 필요 없을 때: 별명과 핀번호
- 붙인 다음에 확인할 것
1. 세 방법 중 무엇을 고르나
AI가 만들어 준 로그인 화면에는 이메일 칸, 비밀번호 칸, 회원가입 버튼이 그럴듯하게 붙어 있습니다. 그런데 그 화면을 돌아가게 만들려면 방식을 먼저 정해야 합니다. 셋 중 무엇으로 갈지가 뒤에 만들 것을 전부 바꿉니다.
질문 세 개면 갈립니다.
플로차트: 위에서부터 예 또는 아니오로 내려간다
- 질문 1 계정을 계속 유지해야 하나 아니오 + 틀려도 잃을 것이 작음 → 6절 별명과 핀번호로 종료
- 질문 2 사용자가 다 가진 계정 제공자가 있나 예 → 3절 소셜 로그인 (구글, 카카오 등)
- 질문 3 비밀번호를 관리할 생각이 있나 예 → 2절 회원가입 / 아니오 → 4절 매직링크. 둘 다 5절 선행 필수
질문 3개, 도착지 4개
세 방법의 장단점을 한 표로 보면 이렇습니다.
| 항목 | 영구 회원가입 | 구글 로그인 | 매직링크 |
|---|---|---|---|
| 사용자가 하는 일 | 이메일, 비밀번호 입력 | 버튼 한 번 클릭 | 이메일 입력, 메일함에서 링크 클릭 |
| 비밀번호 보관 | 내 서비스 (Supabase) | 구글 | 없음 |
| 메일 발송 필요 | 필요 (확인 메일 기본 켜짐) | 불필요 | 필수 |
| 재방문 마찰 | 비밀번호 기억 | 없음 | 매번 메일함 왕복 |
| 막히는 자리 | 확인 메일 미도착 | 그 계정이 없는 사람 | 스팸함 유입, 사내 메일 필터 차단 |
| 추가로 만들 것 | 재설정 메일, 비밀번호 규칙 | 구글 앱 등록, 주소 2곳 등록 | 콜백 페이지, 발송 서비스 |
| 잘 맞는 자리 | 오래 쓰는 서비스, 사내 도구 | 사용자층이 구글 계정 보유 | 이메일 명단이 이미 있는 경우 |
가로가 좁으면 표를 옆으로 밀어 봅니다.
세 방법 모두 Supabase 하나로 됩니다. 아래 코드는 전부 브라우저 쪽에서 도는 자바스크립트이고, 프로젝트 주소와 공개 키만 있으면 그대로 돌아갑니다. 어떤 키를 어디에 두는지는 키 종류를 정리한 글에 적어 뒀습니다.
2. 영구 회원가입: 이메일과 비밀번호
가장 익숙한 방식입니다. 이메일과 비밀번호를 받아 계정을 만들고, 다음부터는 그 둘로 들어옵니다.
async function signUpNewUser() {
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'example-password',
options: { emailRedirectTo: 'https://내사이트/welcome' }
})
}
async function signInWithEmail() {
const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'example-password'
})
}
여기서 처음 만드는 사람이 거의 다 걸리는 지점이 있습니다. Supabase가 호스팅하는 프로젝트는 이메일 확인이 기본으로 켜져 있습니다. 즉 signUp을 부르면 계정만 생기는 게 아니라 확인 메일이 나가고, 사용자가 그 메일의 링크를 눌러야 가입이 끝납니다. 끄고 싶으면 대시보드의 인증 제공자 설정에서 바꿉니다. 그리고 emailRedirectTo에 적은 주소는 Supabase의 리디렉트 허용 목록에도 등록해야 합니다. 등록하지 않으면 사용자가 확인 메일 링크를 눌러도 내 사이트로 돌아오지 못합니다.
확인 메일이 켜져 있을 때 실제로 지나가는 길
- 1. 사용자 이메일과 비밀번호 입력 signUp 호출
- 2. Supabase 확인 메일 발송 여기서 발송 한도에 걸림 (5절)
- 3. 사용자 메일 링크 클릭 emailRedirectTo 주소로 복귀
- 4. 서비스 가입 완료, 세션 발급 이때부터 signInWithPassword 가능
가입은 한 번, 메일 왕복도 한 번
3. 구글 로그인: 주소를 두 군데 등록한다
코드만 보면 셋 중 가장 짧습니다.
await supabase.auth.signInWithOAuth({ provider: 'google' })
만드는 일의 대부분은 코드 밖에 있습니다. 구글에 “내 서비스가 로그인을 요청해도 된다”고 등록하고, Supabase에 구글이 준 열쇠를 넣어 주는 절차입니다. 여기서 주소를 두 개 등록하는데 서로 다른 주소라는 게 핵심입니다.
등록할 것은 주소 2개와 키 1쌍, 넣는 곳이 다르다
- 구글 클라우드 콘솔 승인된 리디렉션 URI 넣을 값: Supabase 콜백 주소 (내 사이트 주소 아님)
- Supabase 대시보드 구글 제공자 화면 넣을 값: 구글이 준 클라이언트 ID와 시크릿
- Supabase 대시보드 리디렉트 허용 목록 넣을 값: 내 사이트 주소
구글에는 Supabase 주소, Supabase에는 내 주소
실제 화면은 이렇게 생겼습니다.
Supabase 콜백 주소는 외우지 않아도 됩니다. 위 화면에서 복사 버튼을 눌러 구글 쪽에 붙이면 됩니다. 반대로 로그인이 끝난 뒤 사용자를 돌려보낼 내 사이트 주소는 Supabase의 리디렉트 허용 목록에 있어야 하고, 코드에서 redirectTo로 지정하는 주소도 그 목록에 있는 것이어야 합니다.
내 사이트 주소를 넣는 자리는 따로 있습니다. Supabase의 URL Configuration 화면입니다.
4. 매직링크: 비밀번호를 아예 없앤다
비밀번호를 만들지 않는 방식입니다. 이메일만 받아서 일회용 링크를 보내고, 사용자가 그 링크를 누르면 로그인이 끝납니다. 비밀번호를 저장하지 않으니 새어 나갈 비밀번호도 없고 재설정 화면도 필요 없습니다.
await supabase.auth.signInWithOtp({
email: 'user@example.com',
options: {
emailRedirectTo: 'https://내사이트/login',
shouldCreateUser: false // 명단에 없는 이메일로는 계정을 만들지 않는다
}
})
함수 이름이 signInWithOtp라 잘못 붙였나 싶지만 맞습니다. 공식 문서가 이름은 OTP인데 기본 동작은 매직링크 발송이라고 밝혀 두었고, 둘은 메일 내용만 다릅니다.
shouldCreateUser가 이 코드에서 가장 중요한 줄입니다. 기본값이 참이라 아무 이메일이나 넣으면 계정이 먼저 만들어집니다. 아래 도식의 명단 대조는 계정이 생긴 뒤에 작동하므로, 명단을 두고 쓰는 서비스라면 이 옵션을 거짓으로 놓아야 합니다.
매직링크 로그인이 실제로 지나가는 길
- 1. 사용자 이메일 입력 비밀번호 칸 없음
- 2. Supabase 일회용 링크 발송 기본 발송기로는 여기서 막힘 (5절)
- 3. 사용자 메일함에서 링크 클릭 이 이탈 구간이 가장 큰 마찰
- 4. 콜백 페이지 세션 수신, 명단 대조 명단에 없으면 즉시 로그아웃
함수는 한 줄, 만들 것은 네 칸
4번이 사람들이 빠뜨리는 자리입니다. emailRedirectTo에 적은 주소에는 돌아온 사용자를 받아 세션을 확인하는 코드가 있어야 합니다. 회원가입과 달리 매직링크는 로그인할 때마다 이 왕복을 하므로, 이 페이지가 어설프면 마찰이 매번 반복됩니다.
5. Resend로 메일 발송 한도를 푼다
앞의 세 방법 중 둘이 메일에 의존합니다. 회원가입은 확인 메일을, 매직링크는 로그인 링크를 보내야 합니다. 그래서 이 절은 둘 중 하나라도 고른 사람에게는 선택이 아니라 필수입니다.
Supabase가 기본으로 주는 메일 발송기에는 벽이 둘 있습니다. 발송 업체를 따로 연결해야 둘 다 풀립니다. 이 연결을 커스텀 SMTP라고 부르는데, 내가 지정한 업체로 메일이 나가도록 설정에 적어 두는 일입니다.
| 벽 | 내용 | 증상 |
|---|---|---|
| 수신자 제한 | 프로젝트 팀원 주소 외 발송 거부 | Email address not authorized 오류 |
| 발송량 제한 | 시간당 2통 | 테스트 몇 번에 소진 |
앞의 것이 먼저 막습니다. 내 계정으로 테스트할 때는 팀원이라 잘 되다가, 다른 사람 이메일을 넣는 순간 발송이 거부됩니다. 오류 문구가 한도 오류와 달라서 “발송 한도” 쪽만 알고 있으면 원인을 못 찾습니다.
벽을 푸는 자리는 대시보드 한 곳입니다.
Supabase의 발송 설정 문서가 발송기 전체를 두고 시간당 2통이라고 적으면서, 예시 업체로 Resend, AWS SES, Postmark, SendGrid 등을 올려 두었습니다. 같은 문서가 기본 발송기는 운영용이 아니라고 못 박습니다. 이 중에서 개인이 붙이기 가장 가벼운 쪽이 Resend입니다.
| 단계 | 하는 일 |
|---|---|
| 1 | Resend 가입, 도메인 등록 |
| 2 | 발송 전용 하위 도메인 지정 (예: send.내도메인) |
| 3 | 도메인 주소록(DNS)에 인증용 레코드 3줄 등록 |
| 4 | 레코드 검증 통과 확인 |
| 5 | 발송 권한만 가진 API 키 발급 |
| 6 | Supabase 발송 설정에 그 값 입력 |
| 7 | 발송 한도 재조정 (연결 직후 시간당 30통으로 걸림) |
6. 로그인이 필요 없을 때: 별명과 핀번호
여기까지 읽고 나면 만들 게 꽤 많다는 게 보입니다. 그래서 한 번 되묻습니다. 내 서비스에 정말 계정이 필요한가.
계정이 하는 일은 두 가지입니다. 하나는 “지금 들어온 사람이 누구인지” 알아내는 것이고, 다른 하나는 “그 사람의 것을 다음에 다시 꺼내 주는” 일입니다. 앞의 것만 필요하다면 회원가입까지 안 가도 됩니다. 글을 쓸 때 별명과 숫자 몇 자리를 같이 받아 두고, 지울 때 그 숫자를 다시 물어보면 됩니다.
이 블로그의 댓글이 그렇게 돌아갑니다. 지금 이 글 아래에도 같은 칸 두 개가 있습니다.
만들 때 지켜야 할 것이 하나 있습니다. 핀번호를 그대로 저장하면 안 됩니다. 무작위 문자열(소금값)을 하나 붙여서 해시로 바꾼 값만 저장하고, 지울 때는 입력받은 숫자에 같은 소금값을 붙여 다시 해시해서 두 값이 같은지만 봅니다. 이렇게 하면 저장된 데이터를 그냥 열어 본 사람이 숫자를 바로 읽지는 못합니다. 다만 자릿수가 짧으면 대입으로 뚫리므로 이걸 안전 보장으로 여기면 안 됩니다. 바로 아래 함정과 같이 읽어야 합니다.
7. 붙인 다음에 확인할 것
로그인이 되면 절반입니다. 배포 전에 아래 넷을 확인합니다.
| 확인할 것 | 안 하면 생기는 일 |
|---|---|
| 리디렉트 허용 목록에 운영 주소 등록 | 로컬 주소만 있어 배포 후 로그인이 안 돌아옴 |
| 다른 사람 이메일로 가입 1회 실측 | 팀원 주소로만 테스트해 발송 거부를 못 봄 |
| 발송 한도가 예상 사용량 이상인지 | 사용자가 몰리는 날 메일이 안 나감 |
| 로그인한 사람별 데이터 잠금 | 남의 데이터가 그대로 보임 |
마지막 줄이 가장 자주 빠집니다. 로그인은 “지금 들어온 사람이 누구인가”까지만 답하고, “그 사람이 어느 데이터까지 볼 수 있는가”는 답하지 않습니다. 화면은 열려 있어도 그 안의 데이터가 사람마다 다르게 보여야 한다면 데이터베이스 쪽에 잠금을 따로 걸어야 합니다.
만들기 전에 한 번 더 물어보는 게 제일 크게 아낍니다. 이 서비스에 정말 계정이 필요한가. 저는 그 질문을 늦게 해서 함수 다섯 개를 지웠습니다.
댓글