Guides

Adding Login to a Vibe-Coded App with Supabase: Sign-Up, Google, Magic Link

15 min read#vibe-coding#supabase#authentication#google-login#magic-link

Who this is forVibe coders who have built the screens with AI but have not yet decided how to add login.

TL;DR: When you ask AI to build a service, a login screen comes along with it, but making that screen actually work is a different job. There are three main ways to add login to a service built with vibe coding: permanent sign-up with email and password, social login that borrows a Google account, and magic links that sign users in through an emailed link with no password. With Supabase, each one takes a function or two, but the places where you get stuck are all different. This post starts with a flowchart to help you pick one, then covers the implementation and pros and cons of each, plus the email send limit that two of the three run into.

Contents

  1. Which method to pick
  2. Permanent sign-up: email and password
  3. Google login: register two URLs
  4. Magic link: remove the password entirely
  5. Lifting the email send limit with Resend
  6. When a login is not needed: nickname and PIN
  7. What to check after wiring it up

1. Which Method to Pick

The login screen that AI builds for you comes with an email field, a password field, and a sign-up button, all looking convincing. But you have to decide on a method before that screen can actually work. Which of the three you choose changes everything you build afterward.

Three questions decide it.

Flowchart: work down from the top, answering yes or no

  1. Question 1 Do you need to keep accounts long-term? No + little to lose if wrong → ends at Section 6, nickname and PIN
  2. Question 2 Is there an account provider that your users all have? Yes → Section 3, social login (Google, Kakao, etc.)
  3. Question 3 Are you willing to manage passwords? Yes → Section 2, sign-up / No → Section 4, magic link. Both require Section 5 first

3 questions, 4 destinations

Here are the pros and cons of the three methods in one table.

ItemPermanent sign-upGoogle loginMagic link
What the user doesEnter email and passwordOne click on a buttonEnter email, click the link in the inbox
Password storageYour service (Supabase)GoogleNone
Needs email sendingRequired (confirmation email is on by default)Not neededRequired
Friction on return visitsRemembering the passwordNoneGoing back to the inbox every time
Where it gets stuckConfirmation email never arrivesPeople without that accountLands in spam, blocked by company mail filters
What else you have to buildReset emails, password rulesRegistering a Google app, registering 2 URLsCallback page, sending service
Best fitLong-running services, internal toolsUsers who already have Google accountsWhen you already have an email list

If the table is too narrow, try scrolling it sideways.

All three methods work with one tool, Supabase. The code below is all JavaScript that runs in the browser, and it works as-is as long as you have the project URL and the public key. I wrote up which key goes where in a post that organizes the key types.

2. Permanent Sign-Up: Email and Password

This is the most familiar method. It takes an email and password to create an account, and from then on, users log in with those two.

async function signUpNewUser() {
  const { data, error } = await supabase.auth.signUp({
    email: 'user@example.com',
    password: 'example-password',
    options: { emailRedirectTo: 'https://mysite/welcome' }
  })
}

async function signInWithEmail() {
  const { data, error } = await supabase.auth.signInWithPassword({
    email: 'user@example.com',
    password: 'example-password'
  })
}

Almost everyone who builds this for the first time gets stuck at the same point. Projects hosted on Supabase have email confirmation turned on by default. That means calling signUp doesn’t just create an account; it also sends a confirmation email, and the user has to click the link in that email to finish signing up. To turn it off, change the setting in the auth provider settings in the dashboard. Also, the address you put in emailRedirectTo must be registered in Supabase’s redirect allow list. If you don’t register it, users can click the confirmation link but won’t make it back to your site.

The path actually taken when confirmation email is on

  1. 1. User Enter email and password signUp call
  2. 2. Supabase Send confirmation email This is where you hit the send limit (Section 5)
  3. 3. User Click the link in the email Return to the emailRedirectTo address
  4. 4. Service Sign-up complete, session issued From here on, signInWithPassword works

Sign-up happens once, and the email round trip happens once

3. Google Login: Register Two URLs

Looking at the code alone, it’s the shortest of the three.

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

Most of the work happens outside the code. You register with Google that “my service is allowed to request logins,” and then you put the keys Google gives you into Supabase. The key point here is that you register two addresses, and they are different addresses.

What to register: 2 addresses and 1 key pair, each going in a different place

  1. Google Cloud Console Authorized redirect URI Value to enter: Supabase callback address (not your site's address)
  2. Supabase dashboard Google provider screen Value to enter: the client ID and secret Google gave you
  3. Supabase dashboard Redirect allow list Value to enter: your site's address

Google gets Supabase's address; Supabase gets your address

Here is what the actual screen looks like.

The Google provider settings panel in the Supabase dashboard. From the top: a toggle to enable Google login, a client ID input field, and a client secret input field. At the very bottom are a callback URL field and a copy button. The callback URL is an address ending in supabase.co, with the project identifier part hidden.
The Google provider panel in the Supabase dashboard. Enter the values Google gave you in the two fields at the top, and copy the callback URL at the very bottom to paste it on the Google side. The project identifier is hidden.

You don’t need to memorize the Supabase callback address. Press the copy button on the screen above and paste it on the Google side. Conversely, the address of your site where users are sent back after login has to be in Supabase’s redirect allow list, and the address you specify as redirectTo in the code also has to be on that list.

The place to enter your site’s address is elsewhere: the URL Configuration screen in Supabase.

The redirect URL allow list screen in the Supabase dashboard. It says wildcards can be used, has an add URL button, and lists two registered addresses. The first address is hidden, and the second is a local development address.
Register your site's address, where users come back after login, here. As in the second line, if you only add the local address, login won't return on the deployed site.

This method doesn’t create a password at all. It takes only an email, sends a one-time link, and when the user clicks that link, login is complete. Because no password is stored, there’s no password to leak and no reset screen is needed.

await supabase.auth.signInWithOtp({
  email: 'user@example.com',
  options: {
    emailRedirectTo: 'https://mysite/login',
    shouldCreateUser: false   // Do not create an account for an email that is not on the roster
  }
})

The function is named signInWithOtp, so it might look like a mistake, but it’s correct. The official docs note that the name is OTP but the default behavior is sending a magic link, and the two differ only in what the email contains.

The most important line in this code is shouldCreateUser. Its default is true, so if you enter any email at all, an account gets created first. The roster check in the diagram below only works after the account exists, so if your service runs on a roster, you need to set this option to false.

The path magic link login actually takes

  1. 1. User Enter email No password field
  2. 2. Supabase Send one-time link The default sender blocks you here (Section 5)
  3. 3. User Click the link in the inbox This drop-off point is the biggest friction
  4. 4. Callback page Receive session, check against the roster Signed out immediately if not on the roster

One line of code, four boxes to build

Step 4 is the one people skip. The address you put in emailRedirectTo needs code that receives the returning user and checks the session. Unlike sign-up, a magic link does this round trip every time the user logs in, so if this page is sloppy, the friction repeats every time.

5. Lifting the Email Send Limit with Resend

Two of the three methods depend on email. Sign-up has to send a confirmation email, and magic links have to send a login link. So for anyone who picked either of those, this section isn’t optional; it’s required.

The email sender that Supabase provides by default has two walls. You have to connect a separate sending provider to clear both. This connection is called custom SMTP, which means setting up your email to go out through a provider you choose.

Wall Details Symptom
Recipient restriction Sending is refused to addresses other than project team members Email address not authorized error
Volume limit 2 emails per hour Used up after a few tests

The first one blocks you first. When you test with your own account, it works because you’re a team member, but the moment you enter someone else’s email, sending is refused. The error message differs from the limit error, so if you only know about the “send limit,” you won’t find the cause.

The authentication send rate limit table from Supabase's official docs. In the limit column of the row for endpoints that trigger email sending, it says 2 per hour, and it notes that this can only be changed by setting up custom SMTP.
The first row covers sends in the sign-up and password reset family, at 2 emails per hour. Right below is the magic link family, at 30 emails per hour. The numbers differ, but both can only be changed by connecting a sending provider. Source: Supabase Auth Rate Limits documentation (checked September 7, 2026)

There is one place in the dashboard where you take down the walls.

The SMTP Settings tab on the Emails screen in the Supabase dashboard. The custom SMTP toggle is off, with a note that auth emails are sent through a custom SMTP provider and a link to the sending limits guide.
The SMTP Settings tab under Emails, under Authentication. Turn on this toggle and enter the sending provider's details, and both walls come down together.

Supabase’s email setup documentation notes that the built-in sender as a whole is limited to 2 emails per hour, and it lists Resend, AWS SES, Postmark, SendGrid, and others as example providers. The same document states plainly that the default sender is not for production. Of these, the lightest for an individual to set up is Resend.

Step What to do
1 Sign up for Resend, add your domain
2 Designate a dedicated sending subdomain (e.g., send.yourdomain)
3 Add 3 verification records to your domain’s DNS
4 Confirm the records pass verification
5 Issue an API key with send-only permission
6 Enter those values in Supabase’s sending settings
7 Readjust the send limit (right after connecting, it’s set to 30 emails per hour)

6. When a Login Is Not Needed: Nickname and PIN

By this point, you can see there’s quite a lot to build. So let me ask once more: does your service really need accounts?

An account does two things. One is figuring out “who is coming in right now,” and the other is handing back “that person’s things the next time.” If you only need the first, you don’t have to go as far as sign-up. When someone writes something, collect a nickname and a few digits together, and when they want to delete it, ask for those digits again.

This blog’s comments work that way. Right below this post, there are the same two fields.

The comment form on this blog. A nickname field and a PIN field of 4 to 8 digits sit side by side, with a comment content field and a submit button below. A note says the PIN is needed to delete your comment.
There's no sign-up and no email. Just two fields, the nickname and PIN, and the PIN is only used when deleting.

There is one thing to get right when you build this. You must not store the PIN as-is. Attach a random string (a salt), store only the hashed result, and when deleting, attach the same salt to the entered digits, hash it again, and check only whether the two values match. This way, someone who opens the stored data can’t read the numbers directly. But if the digit count is short, brute force gets through, so don’t treat this as a safety guarantee. Read it together with the pitfall below.

7. What to Check After Wiring It Up

Once login works, you’re halfway there. Check these four items before deployment.

What to check What happens if you skip it
Register the production address in the redirect allow list Only the local address is there, so login doesn’t return after deployment
Run one real sign-up with someone else’s email Testing only with team member addresses means you never see the send refusal
Confirm the send limit exceeds expected usage On days when users flood in, emails don’t go out
Lock data per logged-in user Other people’s data is visible as-is

The last row is the one most often skipped. Login only answers “who is coming in right now,” and it doesn’t answer “how much of the data can that person see.” Even if the screen is open, if the data inside has to look different for each person, you have to set up locks separately on the database side.

Asking the question one more time before you build saves the most. Does this service really need accounts? I asked it late, and ended up deleting five functions.

Frequently asked questions

Why does my Supabase sign-up succeed but the user still can't log in?
Supabase turns on email confirmation by default. signUp sends a confirmation email, and login stays blocked until the user clicks the link. You can turn it off in the dashboard's auth provider settings.
Which URL do I register with Google for Supabase social login?
Register Supabase's callback address as the authorized redirect URI in Google Cloud Console. Register your site's address in Supabase's redirect allow list instead.

Want the full system? The Claude Code & Codex Skills guidebook collects the skills and subagents behind this blog, from $19.