가이드

바이브코딩 Supabase 로그인 붙이기: 회원가입, 구글, 매직링크

읽는 데 약 10분#바이브코딩#Supabase#회원가입#소셜 로그인#매직링크#로그인

이 글의 독자바이브코딩으로 화면은 만들었는데 로그인을 어떤 방식으로 붙일지 못 정한 사람

한줄 요약: AI에게 서비스를 만들어 달라고 하면 로그인 화면이 딸려 오는데, 그 화면을 실제로 돌아가게 만드는 건 다른 일입니다. 바이브코딩으로 만든 서비스에 로그인을 붙이는 길은 크게 셋입니다. 이메일과 비밀번호를 받는 영구 회원가입, 구글 계정을 빌리는 소셜 로그인, 비밀번호 없이 메일 링크로 들어오는 매직링크입니다. Supabase를 쓰면 셋 다 함수 한두 줄이지만 막히는 자리는 전부 다릅니다. 어느 것을 고를지 정하는 플로차트부터 시작해서, 각각의 구현과 장단점, 그리고 셋 중 둘이 공통으로 부딪히는 메일 발송 한도까지 정리했습니다.

목차

  1. 세 방법 중 무엇을 고르나
  2. 영구 회원가입: 이메일과 비밀번호
  3. 구글 로그인: 주소를 두 군데 등록한다
  4. 매직링크: 비밀번호를 아예 없앤다
  5. Resend로 메일 발송 한도를 푼다
  6. 로그인이 필요 없을 때: 별명과 핀번호
  7. 붙인 다음에 확인할 것

1. 세 방법 중 무엇을 고르나

AI가 만들어 준 로그인 화면에는 이메일 칸, 비밀번호 칸, 회원가입 버튼이 그럴듯하게 붙어 있습니다. 그런데 그 화면을 돌아가게 만들려면 방식을 먼저 정해야 합니다. 셋 중 무엇으로 갈지가 뒤에 만들 것을 전부 바꿉니다.

질문 세 개면 갈립니다.

플로차트: 위에서부터 예 또는 아니오로 내려간다

  1. 질문 1 계정을 계속 유지해야 하나 아니오 + 틀려도 잃을 것이 작음 → 6절 별명과 핀번호로 종료
  2. 질문 2 사용자가 다 가진 계정 제공자가 있나 예 → 3절 소셜 로그인 (구글, 카카오 등)
  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. 1. 사용자 이메일과 비밀번호 입력 signUp 호출
  2. 2. Supabase 확인 메일 발송 여기서 발송 한도에 걸림 (5절)
  3. 3. 사용자 메일 링크 클릭 emailRedirectTo 주소로 복귀
  4. 4. 서비스 가입 완료, 세션 발급 이때부터 signInWithPassword 가능

가입은 한 번, 메일 왕복도 한 번

3. 구글 로그인: 주소를 두 군데 등록한다

코드만 보면 셋 중 가장 짧습니다.

await supabase.auth.signInWithOAuth({ provider: 'google' })

만드는 일의 대부분은 코드 밖에 있습니다. 구글에 “내 서비스가 로그인을 요청해도 된다”고 등록하고, Supabase에 구글이 준 열쇠를 넣어 주는 절차입니다. 여기서 주소를 두 개 등록하는데 서로 다른 주소라는 게 핵심입니다.

등록할 것은 주소 2개와 키 1쌍, 넣는 곳이 다르다

  1. 구글 클라우드 콘솔 승인된 리디렉션 URI 넣을 값: Supabase 콜백 주소 (내 사이트 주소 아님)
  2. Supabase 대시보드 구글 제공자 화면 넣을 값: 구글이 준 클라이언트 ID와 시크릿
  3. Supabase 대시보드 리디렉트 허용 목록 넣을 값: 내 사이트 주소

구글에는 Supabase 주소, Supabase에는 내 주소

실제 화면은 이렇게 생겼습니다.

Supabase 대시보드의 구글 제공자 설정 패널. 위에서부터 구글 로그인 사용 토글, 클라이언트 ID 입력칸, 클라이언트 시크릿 입력칸이 있고 맨 아래에 콜백 URL 칸과 복사 버튼이 있다. 콜백 URL은 supabase.co로 끝나는 주소이고 프로젝트 식별자 부분은 가려져 있다.
Supabase 대시보드의 구글 제공자 패널입니다. 위 두 칸에 구글이 준 값을 넣고, 맨 아래 콜백 URL을 복사해 구글 쪽에 붙입니다. 프로젝트 식별자는 가렸습니다.

Supabase 콜백 주소는 외우지 않아도 됩니다. 위 화면에서 복사 버튼을 눌러 구글 쪽에 붙이면 됩니다. 반대로 로그인이 끝난 뒤 사용자를 돌려보낼 내 사이트 주소는 Supabase의 리디렉트 허용 목록에 있어야 하고, 코드에서 redirectTo로 지정하는 주소도 그 목록에 있는 것이어야 합니다.

내 사이트 주소를 넣는 자리는 따로 있습니다. Supabase의 URL Configuration 화면입니다.

Supabase 대시보드의 리디렉트 URL 허용 목록 화면. 와일드카드를 쓸 수 있다는 설명과 URL 추가 버튼이 있고 등록된 주소 두 개가 목록으로 나열돼 있다. 첫 번째 주소는 가려져 있고 두 번째는 로컬 개발 주소다.
로그인이 끝난 뒤 돌아올 내 사이트 주소는 여기에 등록합니다. 두 번째 줄처럼 로컬 주소만 넣어 두면 배포한 사이트에서는 로그인이 안 돌아옵니다.

4. 매직링크: 비밀번호를 아예 없앤다

비밀번호를 만들지 않는 방식입니다. 이메일만 받아서 일회용 링크를 보내고, 사용자가 그 링크를 누르면 로그인이 끝납니다. 비밀번호를 저장하지 않으니 새어 나갈 비밀번호도 없고 재설정 화면도 필요 없습니다.

await supabase.auth.signInWithOtp({
  email: 'user@example.com',
  options: {
    emailRedirectTo: 'https://내사이트/login',
    shouldCreateUser: false   // 명단에 없는 이메일로는 계정을 만들지 않는다
  }
})

함수 이름이 signInWithOtp라 잘못 붙였나 싶지만 맞습니다. 공식 문서가 이름은 OTP인데 기본 동작은 매직링크 발송이라고 밝혀 두었고, 둘은 메일 내용만 다릅니다.

shouldCreateUser가 이 코드에서 가장 중요한 줄입니다. 기본값이 참이라 아무 이메일이나 넣으면 계정이 먼저 만들어집니다. 아래 도식의 명단 대조는 계정이 생긴 뒤에 작동하므로, 명단을 두고 쓰는 서비스라면 이 옵션을 거짓으로 놓아야 합니다.

매직링크 로그인이 실제로 지나가는 길

  1. 1. 사용자 이메일 입력 비밀번호 칸 없음
  2. 2. Supabase 일회용 링크 발송 기본 발송기로는 여기서 막힘 (5절)
  3. 3. 사용자 메일함에서 링크 클릭 이 이탈 구간이 가장 큰 마찰
  4. 4. 콜백 페이지 세션 수신, 명단 대조 명단에 없으면 즉시 로그아웃

함수는 한 줄, 만들 것은 네 칸

4번이 사람들이 빠뜨리는 자리입니다. emailRedirectTo에 적은 주소에는 돌아온 사용자를 받아 세션을 확인하는 코드가 있어야 합니다. 회원가입과 달리 매직링크는 로그인할 때마다 이 왕복을 하므로, 이 페이지가 어설프면 마찰이 매번 반복됩니다.

5. Resend로 메일 발송 한도를 푼다

앞의 세 방법 중 둘이 메일에 의존합니다. 회원가입은 확인 메일을, 매직링크는 로그인 링크를 보내야 합니다. 그래서 이 절은 둘 중 하나라도 고른 사람에게는 선택이 아니라 필수입니다.

Supabase가 기본으로 주는 메일 발송기에는 벽이 둘 있습니다. 발송 업체를 따로 연결해야 둘 다 풀립니다. 이 연결을 커스텀 SMTP라고 부르는데, 내가 지정한 업체로 메일이 나가도록 설정에 적어 두는 일입니다.

벽 내용 증상
수신자 제한 프로젝트 팀원 주소 외 발송 거부 Email address not authorized 오류
발송량 제한 시간당 2통 테스트 몇 번에 소진

앞의 것이 먼저 막습니다. 내 계정으로 테스트할 때는 팀원이라 잘 되다가, 다른 사람 이메일을 넣는 순간 발송이 거부됩니다. 오류 문구가 한도 오류와 달라서 “발송 한도” 쪽만 알고 있으면 원인을 못 찾습니다.

Supabase 공식 문서의 인증 발송 한도 표. 메일 발송을 일으키는 엔드포인트 행의 한도 칸에 시간당 2통이라고 적혀 있고, 커스텀 SMTP를 설정해야만 바꿀 수 있다고 쓰여 있다.
첫 행이 회원가입과 비밀번호 재설정 계열의 발송으로 시간당 2통, 바로 아래가 매직링크 계열로 시간당 30통입니다. 숫자는 다르지만 둘 다 발송 업체를 연결해야 바뀝니다. 출처: Supabase Auth Rate Limits 문서 (2026년 9월 7일 확인)

벽을 푸는 자리는 대시보드 한 곳입니다.

Supabase 대시보드 Emails 화면의 SMTP Settings 탭. 커스텀 SMTP 사용 토글이 꺼져 있고, 인증 메일을 커스텀 SMTP 제공자로 보낸다는 설명과 발송 한도 안내 링크가 붙어 있다.
Authentication 아래 Emails 화면의 SMTP Settings 탭입니다. 이 토글을 켜고 발송 업체 정보를 넣으면 두 벽이 같이 풀립니다.

Supabase의 발송 설정 문서가 발송기 전체를 두고 시간당 2통이라고 적으면서, 예시 업체로 Resend, AWS SES, Postmark, SendGrid 등을 올려 두었습니다. 같은 문서가 기본 발송기는 운영용이 아니라고 못 박습니다. 이 중에서 개인이 붙이기 가장 가벼운 쪽이 Resend입니다.

단계 하는 일
1 Resend 가입, 도메인 등록
2 발송 전용 하위 도메인 지정 (예: send.내도메인)
3 도메인 주소록(DNS)에 인증용 레코드 3줄 등록
4 레코드 검증 통과 확인
5 발송 권한만 가진 API 키 발급
6 Supabase 발송 설정에 그 값 입력
7 발송 한도 재조정 (연결 직후 시간당 30통으로 걸림)

6. 로그인이 필요 없을 때: 별명과 핀번호

여기까지 읽고 나면 만들 게 꽤 많다는 게 보입니다. 그래서 한 번 되묻습니다. 내 서비스에 정말 계정이 필요한가.

계정이 하는 일은 두 가지입니다. 하나는 “지금 들어온 사람이 누구인지” 알아내는 것이고, 다른 하나는 “그 사람의 것을 다음에 다시 꺼내 주는” 일입니다. 앞의 것만 필요하다면 회원가입까지 안 가도 됩니다. 글을 쓸 때 별명과 숫자 몇 자리를 같이 받아 두고, 지울 때 그 숫자를 다시 물어보면 됩니다.

이 블로그의 댓글이 그렇게 돌아갑니다. 지금 이 글 아래에도 같은 칸 두 개가 있습니다.

이 블로그의 댓글 입력 폼. 별명 칸과 핀번호 숫자 4에서 8자리 칸이 나란히 있고 아래에 댓글 내용 칸과 등록 버튼이 있다. 핀번호는 내 댓글을 지울 때 필요하다는 안내 문구가 붙어 있다.
회원가입도 이메일도 없습니다. 별명과 핀번호 두 칸이 전부이고, 핀번호는 지울 때만 쓰입니다.

만들 때 지켜야 할 것이 하나 있습니다. 핀번호를 그대로 저장하면 안 됩니다. 무작위 문자열(소금값)을 하나 붙여서 해시로 바꾼 값만 저장하고, 지울 때는 입력받은 숫자에 같은 소금값을 붙여 다시 해시해서 두 값이 같은지만 봅니다. 이렇게 하면 저장된 데이터를 그냥 열어 본 사람이 숫자를 바로 읽지는 못합니다. 다만 자릿수가 짧으면 대입으로 뚫리므로 이걸 안전 보장으로 여기면 안 됩니다. 바로 아래 함정과 같이 읽어야 합니다.

7. 붙인 다음에 확인할 것

로그인이 되면 절반입니다. 배포 전에 아래 넷을 확인합니다.

확인할 것 안 하면 생기는 일
리디렉트 허용 목록에 운영 주소 등록 로컬 주소만 있어 배포 후 로그인이 안 돌아옴
다른 사람 이메일로 가입 1회 실측 팀원 주소로만 테스트해 발송 거부를 못 봄
발송 한도가 예상 사용량 이상인지 사용자가 몰리는 날 메일이 안 나감
로그인한 사람별 데이터 잠금 남의 데이터가 그대로 보임

마지막 줄이 가장 자주 빠집니다. 로그인은 “지금 들어온 사람이 누구인가”까지만 답하고, “그 사람이 어느 데이터까지 볼 수 있는가”는 답하지 않습니다. 화면은 열려 있어도 그 안의 데이터가 사람마다 다르게 보여야 한다면 데이터베이스 쪽에 잠금을 따로 걸어야 합니다.

만들기 전에 한 번 더 물어보는 게 제일 크게 아낍니다. 이 서비스에 정말 계정이 필요한가. 저는 그 질문을 늦게 해서 함수 다섯 개를 지웠습니다.

댓글

    핀번호는 내 댓글을 지울 때 필요합니다.

    뉴스레터

    새 글을 메일로 받아보세요

    AI 자동화 튜토리얼과 저자 코멘터리를 보냅니다. 스팸 없이, 새 글이 올라올 때만.

    구독하기 ›