목차
Supabase 로그인 · 월간클로드 3회에서 다룬 내용
내 사이트에 로그인 붙이기, 비밀번호 없이 메일로 받은 코드로
Supabase 로그인을 비밀번호 없이 메일 코드로 — 로그인 메일을 누가 보낼지(기본 메일 · Gmail · Resend) 비교부터, 명단에 있는 사람만 들어오게 막는 훅, 6자리 코드 메일, 로그아웃과 윗줄까지.
이 장을 하기 전에
| 있어야 할 것 | 왜 |
|---|---|
| 인터넷에 올라간 내 사이트 · 그 폴더를 Claude Code 로 열 수 있다 | 로그인 화면은 Claude Code가 만든다 |
| Supabase 프로젝트 하나 | 로그인 · 명단 · 메일 설정을 여기서 한다 |
| 로그인할 사람 명단 — 이메일 주소 | 명단에 있는 사람만 들어오게 한다(3.3절) |
| (Gmail로 보낼 거면) 구글 계정 | 로그인 코드 메일을 내 Gmail로 보낸다(3.5절) |
| (Resend로 보낼 거면) 내 도메인 | 메일 서비스는 내 도메인 주소로 보낸다 — 도메인이 없다면 2장부터 |
걸리는 시간은 1시간 안팎입니다. 로그인 화면의 코드는 Claude Code가 씁니다. 우리는 대시보드에서 누를 것과 확인할 것을 따라갑니다.
3.1 먼저 고른다 — 로그인 메일은 누가 보내나
이 장의 로그인은 비밀번호를 쓰지 않습니다. 이메일을 넣으면 6자리 코드가 메일로 오고, 그 코드를 넣으면 들어갑니다. 그러니 로그인은 결국 메일 한 통에 달려 있습니다.
그 메일을 보내 주는 곳이 셋 있습니다. 이것부터 고르고 시작합니다.

- Supabase 기본 메일은 설정이 없어도 되지만, 프로젝트 팀원 주소에만 갑니다. 다른 주소는 「Email address not authorized」 로 거절됩니다. 한도도 시간당 2통입니다. 나 혼자 시험할 때만 씁니다.
- Gmail은 가입할 것이 없어 빠릅니다. 대신 받는 사람에게 내 개인 Gmail 주소가 보이고, 내 구글 계정에 들어가는 비밀번호를 하나 더 만들어야 합니다.
- Resend는 메일 보내기 전문 서비스입니다. 내 도메인 주소(
no-reply@내도메인)로 보내고, 무료로 하루 100통 · 한 달 3,000통까지 됩니다.
외부 메일(Gmail이나 Resend)을 붙이면 Supabase는 시간당 30통으로 한도를 올립니다. 더 필요하면 Authentication → Rate Limits 에서 바꿉니다.
💡 Gmail로 먼저 돌리고, 알리기 전에 Resend로
처음엔 Gmail로 붙여 로그인이 도는지부터 봅니다. 준비가 가장 적습니다.
그리고 사람들에게 「로그인하세요」 하고 알리기 전에 Resend로 바꿉니다. 알린 뒤에 보내는 주소가 바뀌면 받는 사람이 헷갈리고, 처음 보는 주소라 스팸함으로 갈 수도 있습니다.
3.2 로그인 한눈에 — 두 가지를 따로 만든다

📖 OTP(한 번 쓰는 코드)
한 번 쓰고 버리는 숫자 코드. 은행 앱의 인증번호와 같습니다. 정식 이름은 One-Time Password입니다.
이 장의 코드는 6자리, 10분 동안만 쓸 수 있게 만듭니다(3.7절).
이 그림에서 가장 중요한 것은 아래 두 칸입니다. 로그인과 권한은 따로입니다.
📖 로그인과 권한
로그인은 「이 사람이 누구인지」 확인하는 일, 권한은 「이 사람이 무엇을 볼 수 있는지」 정하는 일입니다. 건물로 치면 출입증을 받는 것과, 그 출입증으로 열리는 방이 정해지는 것의 차이입니다.
로그인만 붙이고 권한을 안 만들면, 들어와도 아무것도 열리지 않습니다.


이 장은 로그인(①~⑤)을 끝까지 만들고, 권한은 3.10절에서 짧게 봅니다.
3.3 명단 만들기 — 들어올 사람을 표에 적는다
누구나 들어오는 로그인이 아니라, 정해진 사람만 들어오게 합니다. 그 사람들을 Supabase 안에 표 하나로 적어 둡니다. 이메일과 이름 두 칸이면 됩니다.
표는 Claude Code에게 만들어 달라고 합니다. 이걸 붙이면 Supabase에서 실행할 SQL (데이터베이스에 내리는 명령문)을 먼저 보여 줍니다.
Supabase 에 로그인할 수 있는 사람 명단 표를 만들어줘.
이메일 · 이름 칸. 로그인한 사람은 자기 줄만 읽을 수 있게.
실행할 SQL 을 먼저 보여줘. 내가 읽고 실행할게.
「먼저 보여줘」 가 중요합니다. 무엇을 만드는지 읽고 나서 Supabase의 SQL Editor에 붙여 실행합니다. 모르는 말이 있으면 그 자리에서 Claude에게 물어봅니다.
명단은 대시보드 Table Editor(표 화면)에서 한 줄씩 넣고 뺍니다. 엑셀에 줄을 더하듯이 합니다.
3.4 명단에 없으면 막기 — 「가입 전 확인」 훅
명단 표만 있다고 막히지는 않습니다. 로그인 코드를 달라고 하면 Supabase는 처음 보는 이메일에도 계정을 만들고 메일을 보냅니다. 그 앞에 문지기를 세웁니다.
📖 훅(hook)
정해진 일이 일어나기 직전에 한 번 물어보는 자리. 여기에 함수를 걸어 두면 Supabase가 그 일을 하기 전에 함수에게 「해도 돼?」 하고 묻습니다.
이 장은 Before User Created(사용자를 만들기 직전) 훅을 씁니다.
문지기 역할을 할 함수는 Claude Code에게 부탁합니다.
명단 표에 없는 이메일이면 거절하는 Supabase
「Before User Created」 훅용 Postgres 함수를 만들어줘.
거절할 때 문구는 "수강생만 들어올 수 있어요".
실행할 SQL 을 먼저 보여줘.
SQL을 실행해 함수를 만든 뒤, 대시보드에서 훅에 겁니다. Authentication → Auth Hooks → Add hook → Before User Created hook 입니다.


이제 명단에 없는 이메일은 계정이 안 만들어지고, 코드 메일도 안 갑니다. 이 훅은 무료 요금제에서 됩니다.
💡 「새 가입 허용」 은 켜 둔다
막으려고 Authentication → Sign In / Providers 의 「Allow new users to sign up」 을 끄고 싶어집니다. 끄면 안 됩니다. 명단에 있는 사람도 처음 로그인할 때 계정이 만들어져야 합니다.
문은 열어 두고, 누가 들어올지는 훅이 가립니다.

3.5 메일 보내는 곳 붙이기 ① Gmail
Gmail로 보내려면 Supabase에 내 Gmail에 들어갈 열쇠를 줘야 합니다. 평소 쓰는 구글 비밀번호를 주면 안 됩니다. 이 용도로만 쓰는 앱 비밀번호를 따로 만듭니다.
📖 앱 비밀번호
다른 프로그램이 내 구글 계정을 쓸 수 있게 따로 만드는 16자리 비밀번호. 본 비밀번호는 그대로 두고, 이 프로그램용 열쇠를 하나 더 깎는 셈입니다.
2단계 인증이 켜진 계정에서만 만들 수 있습니다.
구글 계정에서 — 앱 비밀번호 만들기
myaccount.google.com/security 에서 시작합니다.


| 번호 | 할 일 | 됐으면 |
|---|---|---|
| 1 | 보안 화면에서 2단계 인증이 켜져 있는지 본다 | 「사용 설정일」 이 보인다 |
| 2 | 위 검색칸에 앱 비밀번호 를 친다 | 구글 비밀번호를 한 번 더 묻는다 |
| 3 | 비밀번호를 넣는다 | 「앱 비밀번호」 화면 |
| 4 | 앱 이름에 supabase 내사이트 처럼 적고 만들기 | 16자리 비밀번호 창이 뜬다 |
| 5 | 16자리를 복사해 두고 창을 닫는다 | 목록에 이름만 남는다 |
16자리 창에는 이 비밀번호가 Google 계정에 대한 완전한 액세스 권한을 준다고 적혀 있습니다. 창을 닫으면 다시 볼 수 없습니다.
⚠️ 여기서 막힙니다 — 16자리 창을 찍었다 · 대화창에 붙였다
16자리 창은 캡처하지 않고, Claude와의 대화창에도 붙이지 않습니다. 붙이는 순간 그 비밀번호는 안전하다고 볼 수 없습니다.
이미 붙였다면 그 비밀번호를 지우고 새로 만듭니다. 앱 비밀번호는 언제든 지우고 다시 만들 수 있고, 새로 만들면 Supabase의 비밀번호 칸만 바꿔 넣으면 됩니다.

Supabase에서 — SMTP 칸 채우기
📖 SMTP(에스엠티피)
메일을 보내는 표준 방법. 우체국 창구의 접수 규칙과 같습니다. 창구 주소(Host), 창구 번호(Port), 누가 보내는지(Username · Password)를 알려 주면 그 창구로 메일을 부칩니다.
Supabase → Authentication → Emails → SMTP Settings 에서 「Enable custom SMTP」 를 켜면 칸이 열립니다.
| 칸 | 넣을 값 |
|---|---|
| Sender email address | 앱 비밀번호를 만든 그 Gmail 주소 |
| Sender name | 받는 사람에게 보일 이름 (예: 월간클로드) |
| Host | smtp.gmail.com |
| Port number | 465 |
| Minimum interval per user | 60 — 같은 사람에게 60초 안에 다시 보내지 않는다 |
| Username | 그 Gmail 주소 |
| Password | 방금 복사한 16자리 앱 비밀번호 |

그림 3-11의 ② 경고는 정상입니다. Gmail은 개인이 쓰는 메일이라 로그인 코드 같은 메일을 대량으로 보내기에는 맞지 않다는 뜻입니다. 3.1절에서 Gmail을 「먼저 돌려 볼 때」 로 둔 이유입니다.
저장하면 비밀번호 칸은 점으로 바뀌고 「Stored password is hidden」 이라고 나옵니다. 바꾸려면 새 앱 비밀번호를 넣고 다시 저장합니다.
3.6 메일 보내는 곳 붙이기 ② Resend
Resend는 내 도메인 주소로 보냅니다. 내 도메인이 아직 없다면 2장부터 봅니다.
3.7 메일을 「코드」 로 바꾸기 — 템플릿 두 개
Supabase가 보내는 로그인 메일은 기본으로 링크 메일입니다. 누르면 들어가는 링크 하나가 옵니다. 이 장은 이걸 6자리 코드 메일로 바꿉니다.
링크보다 코드를 고르는 이유가 있습니다. 메일 앱 안에서 링크를 누르면 그 메일 앱 속 작은 브라우저에서 로그인되고, 평소 쓰는 브라우저에는 로그인이 남지 않을 수 있습니다. 코드는 로그인 화면이 열린 그 창에서 넣습니다.
코드 길이와 유효 시간
Authentication → Sign In / Providers → Email 을 열고 아래로 내립니다.

| 칸 | 바꾼 값 | 왜 |
|---|---|---|
| Email OTP length | 6 | 넣기 쉬운 길이 |
| Email OTP expiration | 600초(10분) | 로그인 화면을 보며 바로 넣는 코드라 짧게 |
Supabase 문서는 「6자리 코드」 라고 설명하지만, 대시보드에는 다른 값이 들어 있을 수 있습니다. 내 화면의 숫자를 봅니다. 유효 시간 칸은 링크 메일의 유효 시간에도 같이 쓰입니다.
템플릿 바꾸기
📖 메일 템플릿
Supabase가 보내는 메일의 틀. 제목과 본문이 적혀 있고, 사람마다 달라지는 자리는
{{ .Token }}같은 빈칸 표시로 비워 둡니다. 보낼 때 그 자리에 실제 값이 들어갑니다.
Authentication → Emails → Templates 에서 바꿉니다. 핵심은 하나입니다. 링크 자리
{{ .ConfirmationURL }} 를 빼고, 코드 자리 {{ .Token }} 를 넣습니다.

제목과 본문은 이렇게 두었습니다. 이걸 넣으면 그림 3-15의 메일이 옵니다.
제목: 월간클로드 로그인 코드 {{ .Token }}
<h2>월간클로드 로그인 코드</h2>
<p>로그인 화면에 아래 6자리 코드를 넣어 주세요.</p>
<p style="font-size:28px;letter-spacing:6px"><b>{{ .Token }}</b></p>
<p>코드는 10분 동안 쓸 수 있어요.<br>
로그인을 요청하지 않았다면 이 메일은 지우셔도 됩니다.</p>
⚠️ 여기서 막힙니다 — 처음 로그인할 때만 링크 메일이 온다
템플릿이 두 개입니다. 「Magic link or OTP」 와 「Confirm sign up」. 그림 3-7의 Confirm email 이 켜져 있으면, 처음 로그인하는 사람에게는 「Confirm sign up」 이 가고 두 번째부터 「Magic link or OTP」 가 갑니다.
하나만 바꾸면 첫 로그인에 링크 메일이 갑니다. 둘 다 같은 내용으로 바꿉니다.
3.8 로그인 화면 — Claude Code에게
화면은 Claude Code가 만듭니다. 이걸 붙이면 로그인 화면과, 로그인한 뒤 원래 보던 페이지로 돌아오는 길까지 만들어집니다.
Supabase 로 "이메일 → 6자리 코드 → 들어가기" 로그인 화면을 만들어줘.
명단에 없으면 "수강생만 들어올 수 있어요" 를 보여 주고,
코드는 10분, 다시 받기는 60초 뒤에 되게.
로그인하면 원래 보던 페이지로 돌아가게.
만들어졌으면 세 가지를 시험합니다.
| 번호 | 시험 | 됐으면 |
|---|---|---|
| 1 | 명단에 없는 주소를 넣는다 | 「수강생만 들어올 수 있어요」 · 메일이 안 온다 |
| 2 | 명단에 있는 주소 → 메일의 코드를 넣는다 | 들어가고, 보던 페이지로 돌아온다 |
| 3 | 로그아웃하고 다시 로그인한다 | 두 번째 메일도 첫 메일과 같은 모양이다(3.7절) |
💡 계정을 새로 만들지 않고 「다른 사람」 으로 시험하기
Gmail은 주소 가운데에
+아무글자를 넣어도 같은 받은편지함으로 옵니다.내메일+test@gmail.com으로 보낸 메일도내메일@gmail.com에 도착합니다.그런데 Supabase는 둘을 다른 주소로 봅니다. 명단에 없는 사람을 시험할 때 계정을 새로 만들 필요가 없습니다.



그림 3-15의 ①이 3.1절 비교표의 「보낸 사람」 줄입니다. Gmail로 보내면 받는 사람에게 내 개인 주소가 보이고, 보낸 코드 메일은 내 Gmail 「보낸편지함」 에도 쌓입니다.
3.9 로그인 뒤 — 유지 · 로그아웃 · 윗줄
로그인은 얼마나 유지되나
같은 브라우저에서는 계속 유지됩니다. 사이트에 올 때마다 코드를 받을 필요는 없습니다. 다시 로그인해야 하는 때는 이렇습니다.
| 다시 로그인하는 때 | 왜 |
|---|---|
| 로그아웃했다 | 로그인 기록을 지웠다 |
| 다른 기기 · 다른 브라우저 | 로그인은 브라우저마다 따로 남는다 |
| 시크릿 창 | 창을 닫으면 기록이 사라진다 |
| 브라우저 방문 기록 · 사이트 데이터를 지웠다 | 로그인 기록도 같이 지워졌다 |
로그아웃은 어디까지 풀리나
⚠️ 여기서 막힙니다 — PC에서 로그아웃했더니 폰도 풀렸다
Supabase의 로그아웃은 기본값이 「모든 기기에서」 입니다. 한 곳에서 로그아웃하면 그 사람이 로그인해 둔 다른 기기까지 전부 풀립니다.
보통은 「이 브라우저만」 이 맞습니다. Claude Code에게
로그아웃할 때 이 브라우저만 로그아웃되게 해줘 (scope local)라고 부탁합니다.
로그인했는지 화면으로 알 수 있어야 한다
로그인했는데 윗줄에 여전히 「로그인」 단추가 있으면, 로그인이 된 건지 알 수 없습니다. 로그인 화면에 다시 가 보면 코드 넣는 칸 대신 「로그인되어 있어요」 가 먼저 나와서 더 헷갈립니다.
그래서 로그인하면 윗줄의 「로그인」 이 이름으로 바뀌게 합니다(그림 3-4의 ①). 이름을 누르면 로그아웃이 나오게 합니다.
로그인하면 윗줄의 "로그인" 버튼을 로그인한 사람 이름으로 바꿔줘.
이름을 누르면 "로그아웃" 이 나오게.
3.10 (다음 이야기) 로그인한 사람에게만 보이는 글
그림 3-3처럼 로그인만으로는 잠긴 글이 열리지 않습니다. 권한을 붙이는 일은 이 장의 범위를 넘습니다. 여기서는 어떻게 생겼는지만 봅니다.
잠긴 글은 사이트 파일에 넣지 않습니다. 사이트 파일에 본문을 넣고 화면에서만 가리면, 주소만 알아도 읽힙니다. 본문은 Supabase의 표에 두고, 그림은 비공개 칸에 둡니다. 그리고 「로그인했고 명단에 있는 사람만 읽는다」 는 규칙을 겁니다.
📖 RLS(행 수준 보안)
표의 줄마다 누가 읽고 쓸 수 있는지 정하는 규칙. 정식 이름은 Row Level Security 입니다. 규칙이 없으면 아무도 못 읽고, 규칙을 건 만큼만 열립니다.

잠긴 글의 본문이 사이트 파일에 없으니, 검색 로봇도 이 글을 읽지 못합니다. 누구나 보는 공개 페이지를 검색에 등록하는 일은 1장에 있습니다.
⚠️ 여기서 막힙니다 — secret 키를 대화창에 붙였다
표에 글을 올리려면 Supabase의 secret 키가 필요합니다. 이 키는 규칙(RLS)을 건너뛰어 명단까지 다 읽고 쓸 수 있습니다. 대화창 · 캡처에 올리지 않습니다.
올렸다면 Settings → API Keys 에서 새 secret 키를 만들어 바꿔 넣고, 옛 키를 지웁니다. 키 이름에는 소문자 · 숫자 · 밑줄(
_)만 됩니다. 이때 위쪽 Publishable key 는 지우면 안 됩니다 — 로그인 화면이 씁니다.
3.11 정리
- 로그인 코드는 메일로 간다. 보낼 곳부터 고른다 — 기본 메일은 팀원에게만, Gmail로 먼저 돌리고, 알리기 전에 Resend로
- 로그인(누구인지)과 권한(무엇을 볼 수 있나)은 따로 만든다
- 들어올 사람은 명단 표에 적는다. SQL은 먼저 보여 달라고 하고 읽고 실행한다
- 명단에 없으면 Before User Created 훅이 막는다. 「새 가입 허용」 은 켜 둔다
- Gmail은 2단계 인증 → 앱 비밀번호. 16자리는 찍지도, 붙이지도 않는다
- 코드 길이 · 유효 시간은 내 화면의 숫자를 보고 6자리 · 10분으로
- 템플릿은 두 개 다 코드로 — 「Magic link or OTP」 · 「Confirm sign up」
- 시험은 + 별칭으로. 없는 사람 · 있는 사람 · 다시 로그인 세 번
- 로그아웃은 기본이 모든 기기 — 「이 브라우저만」 으로
- 로그인하면 윗줄이 이름으로 바뀌어야 한다
이 장에서 나온 말
| 말 | 쉬운 뜻 |
|---|---|
| OTP | 한 번 쓰고 버리는 숫자 코드. 이 장은 6자리 · 10분 |
| 로그인 · 권한 | 누구인지 확인하기 · 무엇을 볼 수 있는지 정하기. 따로 만든다 |
| 명단 표 | 들어올 수 있는 사람의 이메일을 적은 Supabase 표 |
| SQL | 데이터베이스에 내리는 명령문. Claude가 쓰고, 읽은 뒤 실행한다 |
| 훅 | 정해진 일이 일어나기 직전에 한 번 물어보는 자리 |
| 앱 비밀번호 | 다른 프로그램용으로 따로 만드는 구글 16자리 비밀번호 |
| SMTP | 메일을 보내는 표준 방법. Host · Port · Username · Password |
| 메일 템플릿 | 보내는 메일의 틀. {{ .Token }} 자리에 코드가 들어간다 |
| RLS | 표의 줄마다 누가 읽고 쓸 수 있는지 정하는 규칙 |
3.12 안 될 때
| 증상 | 이렇게 |
|---|---|
| 코드 메일이 안 온다 | 스팸함을 봅니다 · 60초 안에 다시 요청하지 않았는지 · 명단에 있는 주소인지 · 외부 메일을 안 붙였다면 기본 메일은 팀원 주소에만 갑니다 |
| 「Email address not authorized」 | 외부 SMTP가 꺼져 있습니다. 3.5절(또는 3.6절)로 붙입니다 |
| 처음 로그인할 때만 링크 메일이 온다 | 「Confirm sign up」 템플릿을 안 바꿨습니다(3.7절) |
| 코드가 8자리로 온다 | Email OTP length 를 6으로(그림 3-12) |
| 로그인 화면에 코드 넣는 칸이 없다 | 이미 로그인된 상태입니다. 윗줄에 이름이 보이게 하면 헷갈리지 않습니다(3.9절) |
| 로그인했는데 글이 아직 잠겨 있다 | 로그인과 권한은 따로입니다. 권한(규칙)을 아직 안 만들었습니다(3.10절) |
| 한 곳에서 로그아웃했더니 다른 기기도 풀렸다 | 로그아웃 기본값이 모든 기기입니다. 「이 브라우저만」 으로(3.9절) |
| 비밀번호 · 키를 대화창에 붙였다 | 새로 만들어 바꿔 넣고 옛것은 지웁니다 |
| 화면이 뭘 말하는지 모르겠다 | 화면을 캡처해서 Claude에게 붙이고 이 화면에서 뭘 봐야 돼? — 단, 비밀번호 · 키가 보이는 화면은 빼고 |
이 장에서 다루지 않는 것
비밀번호로 로그인하기 · 구글 · 카카오 같은 소셜 로그인, Supabase 프로젝트 만들기, 잠긴 글을 올리고 그리는 방법(3.10절은 개념만), 메일 서비스의 스팸 관리는 이 장에서 다루지 않습니다.
더 읽을 곳 (공식 문서)
| 번호 | 주소 |
|---|---|
| Supabase — 외부 SMTP (기본 메일 제한 · 한도) | https://supabase.com/docs/guides/auth/auth-smtp |
| Supabase — 비밀번호 없는 이메일 로그인 | https://supabase.com/docs/guides/auth/auth-email-passwordless |
| Supabase — 메일 템플릿 | https://supabase.com/docs/guides/auth/auth-email-templates |
| Supabase — Before User Created 훅 | https://supabase.com/docs/guides/auth/auth-hooks/before-user-created-hook |
| Supabase — 로그아웃 범위 | https://supabase.com/docs/reference/javascript/auth-signout |
| 구글 — 앱 비밀번호 | https://support.google.com/accounts/answer/185833?hl=ko |
| 구글 — Gmail 보내기 한도 | https://support.google.com/mail/answer/22839?hl=ko |
| Resend — Supabase SMTP 연결 | https://resend.com/docs/send-with-supabase-smtp |
| Resend — 무료 한도 | https://resend.com/docs/knowledge-base/account-quotas-and-limits |