aliteq.

"Add login with Supabase": the same prompt, three ways

Every vibe coder types "add login with Supabase" and gets working code. Here's what changes when you ask for it vaguely, specifically, and with the guardrails spelled out — with the real code each version produced.

Sam OrtegaUpdated 1h ago6 min readWeb story
Isometric diorama of a glowing key turning in a login card, near-black background, indigo-violet with a small coral accent
Share

"Add login with Supabase" might be the most-typed prompt in vibe coding, and Supabase earns it: the auth is genuinely three lines. But those three lines are the easy 10%. The other 90% — which key the browser sees, what happens when the password is wrong, whether a new user has to confirm their email — is decided by your prompt, or, if you don't decide, by the model's best guess.

We build this site the same way, so here's the honest version: the same request, given three ways to Claude via Claude Code, with the real code it produced each time. Watch what gets pinned down as the prompt gets more specific.

The same request, three ways

"Add login with Supabase", three ways

Each prompt was given to Claude, via Claude Code (26 Sep 2026) on its own. Outputs pasted verbatim, trimmed only with ….

You
add login with supabase
Model
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);

async function login(email, password) {
  const { data } = await supabase.auth.signInWithPassword({ email, password });
  return data;
}

worseIt logs in, but SUPABASE_KEY is undefined, the error is thrown away, and there's no sign-up, confirmation or redirect. A demo, not a feature.

You
add email + password login with Supabase: read the keys from env, show errors, redirect to /dashboard on success, and include a sign-up option
Model
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
);

async function login(email, password) {
  const { error } = await supabase.auth.signInWithPassword({ email, password });
  if (error) return showError(error.message);
  window.location.href = '/dashboard';
}

async function signUp(email, password) {
  const { error } = await supabase.auth.signUp({ email, password });
  if (error) return showError(error.message);
  showMessage('Check your email to confirm your account.');
}

it dependsNow it reads the anon key from an env var, surfaces errors, redirects on success, and offers sign-up with email confirmation.

You
same login, but: only ever use the anon key in the browser (never the service key), add a loading state that disables the button while submitting, and handle the 'user already registered' case
Model
// Browser uses the ANON key only. The service key never leaves the server.
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
);

async function submit(mode, email, password, setLoading) {
  setLoading(true);
  try {
    const fn = mode === 'signup' ? 'signUp' : 'signInWithPassword';
    const { error } = await supabase.auth[fn]({ email, password });
    if (error) {
      if (error.message.includes('already registered'))
        return showError('That email is already in use — try logging in.');
      return showError(error.message);
    }
    window.location.href = mode === 'signup' ? '/check-email' : '/dashboard';
  } finally {
    setLoading(false);
  }
}

betterThe comment on line 1 is the whole security point: the browser gets the anon key, never the service key. Plus a loading guard and the 'already registered' case a real user will hit.

What changed: Supabase makes login three lines. The spec is everything else: which key, what errors, what happens on success, and the edge cases a stranger will hit before you do.

The jump from vague to constrained didn't need a smarter model. It needed you to say the parts a careful teammate would ask about: the key, the failure path, the loading state, the duplicate-email case. Each sentence you add is one fewer thing the AI decides for you.

The single most important line is that comment about the anon key. Supabase gives you two keys, and mixing them up is the classic vibe-coding breach: the anon (publishable) key is designed to be public and is safe to ship to the browser, but only when Row Level Security guards your tables. The service key bypasses all of that and must live only on the server.

One habit worth stealing: after the AI writes your login, ask it the verify-loop question, "what happens if the email is empty, the password is wrong, or the same person submits twice quickly?" On our own build, that single prompt is what turned a login that worked into a login that didn't fall over. The model is happy to attack its own code; you just have to ask it to.

Before you ship it

Login proves who someone is; it does not decide what they're allowed to touch. That's a separate job, and it's where most "my data leaked" stories actually begin. Before real users hit your login page:

Start from What is login, actually? if you want the mental model (it has an interactive two-phone demo of tokens), then come back and prompt for the version that's safe to ship. The pattern repeats for every provider: the auth is easy, the spec is the product.

Common questions

Is the Supabase anon key safe to put in the browser?
Yes, by design — it's the publishable key and acts as a project identifier. But it's only safe if Row Level Security is enabled on your tables; without RLS it can read whole tables.
What's the difference between the anon key and the service key?
The anon (publishable) key respects Row Level Security and is meant for the browser. The service key bypasses RLS entirely and must stay on the server, never in client code.
Does adding Supabase login secure my app?
No. Login is authentication (who you are). You still need authorization (what you're allowed to access), which on Supabase means Row Level Security policies on every table.
How do I prompt an AI for safer login code?
Name the constraints: use only the anon key in the browser, handle errors and the already-registered case, add a loading state, and require email confirmation. The specifics are what make it production-ready.

Found this useful? Share it

Share
Sam Ortega

Build Editor

Sam Ortega

Sam explains what's actually happening when you build software by talking to an AI — what the model is doing, what's really running your app, and where the sharp edges are. No jargon without a picture, no hype, and an honest 'hire someone' when that's the answer.

The Aliteq brief

The tech worth knowing — hardware, AI, gaming, deals. No spam, unsubscribe anytime.

Keep reading