Vibe Code User Authentication (Without Leaking Everyone's Data)

Add login to an AI-built app — and fix the authorization gap that AI tools leave open in almost every project.

C
CodeIllusion Team
#vibe-coding #authentication #security #supabase #tutorial
Vibe Code User Authentication (Without Leaking Everyone's Data)

This is the point where vibe coding stops being risk-free. Up to here, the worst case was a broken page. From here, the worst case is a stranger reading your users’ data.

The good news is the failure is specific and predictable. Here’s what goes wrong and exactly how to catch it.

Don’t Let AI Invent Auth

First rule: use an established provider. Ask for Supabase Auth, Clerk, Auth.js, or Better Auth by name.

Password hashing, session rotation, timing-safe comparison, token expiry — these are solved, and the mistakes are subtle and silent. An AI-written auth system will work fine in testing and fail in ways you won’t discover until it matters.

Add authentication using Supabase Auth. Email and password plus Google OAuth. Include sign up, sign in, sign out, and password reset. Protect the /dashboard route so unauthenticated users are redirected to /login. Use the server client for session checks, not client-side only.

That last sentence prevents a specific bug: client-side-only route protection, where the “protected” page is fully readable by anyone who disables JavaScript or reads the network response.

The Gap That Matters

Here’s what will be wrong. Not might be — will be.

Your app has projects. Users should see their own. The AI writes:

// app/api/projects/[id]/route.ts
export async function GET(req, { params }) {
  const { data: { user } } = await supabase.auth.getUser()
  if (!user) return new Response('Unauthorized', { status: 401 })

  const { data } = await supabase
    .from('projects')
    .select('*')
    .eq('id', params.id)     // ← filters by project, not by owner
    .single()

  return Response.json(data)
}

This checks you’re logged in. It does not check the project is yours. Any authenticated user can read any project by changing the number in the URL.

The app works perfectly. Every feature functions. Every user can read every other user’s data.

The fix is one clause:

    .eq('id', params.id)
    .eq('user_id', user.id)   // ← the line that was missing

Ask For It Explicitly

Every query that reads or writes user-owned data must filter by the authenticated user’s ID at the database level — not in the UI, not after fetching. Go through every route handler and show me each place this is enforced. List any that don’t.

Then read the list yourself. Don’t take “all good” as an answer without seeing the queries.

Enable Row-Level Security

If you’re on Supabase, this is the safety net, and it’s frequently left off because enabling it mid-build causes errors.

Your Supabase anon key is public — it ships to the browser by design. What stops someone querying your entire database from their console is RLS. Without it, your tables are open.

Enable row-level security on every table. Write policies so authenticated users can only select, insert, update and delete rows where user_id matches auth.uid(). Show me the SQL for each policy.

Check it yourself: Supabase dashboard → Table Editor → look for the RLS badge on every single table. No badge means no protection.

The Service Role Key

Supabase gives you a service_role key that bypasses all RLS. It belongs on the server, in an environment variable without the NEXT_PUBLIC_ prefix, and nowhere else.

AI tools reach for it when RLS policies block something, because it makes the error go away. That “fix” removes all your access control at once.

grep -rn "service_role\|SERVICE_ROLE" --include="*.ts" --include="*.tsx" .

Every result should be in server-only code. If any is in a component that renders in the browser, rotate the key immediately.

Test It Properly

Not optional, and it takes five minutes.

  1. Create account A. Make a record. Note its ID.
  2. Create account B in a different browser or private window.
  3. As B, visit /projects/[A's id] directly.
  4. As B, call /api/projects/[A's id] directly.
  5. Log out entirely. Call the API again.

All three should fail. If any returns data, you have the vulnerability.

Then check the database layer:

Give me a curl command that queries the projects table directly using the public anon key, without any auth token.

Run it. It should return nothing.

Other Things That Get Skipped

Email verification. Ask for it explicitly or anyone can sign up as anyone.

Rate limiting on login. Without it, password guessing is unlimited.

Add rate limiting to sign in and password reset — 5 attempts per email per 15 minutes.

Server-side session checks. A useEffect that redirects unauthenticated users is a suggestion, not a control. The data was already sent.

Sensible error messages. “No account with that email” tells an attacker which addresses are registered. Keep login errors generic.

The Checklist

  • Using an established auth provider, not custom code
  • Route protection enforced server-side
  • Every user-data query filters by authenticated user ID
  • RLS enabled on every table, policies written
  • Service role key server-side only
  • Two-account test passed on both page and API routes
  • Logged-out API test returns 401
  • Direct database query with anon key returns nothing
  • Email verification on
  • Rate limiting on auth endpoints
  • Login errors don’t reveal which emails exist

The Honest Framing

Everything above is one afternoon. The vulnerability it prevents is the difference between a side project and a data breach involving other people.

You can vibe code the entire rest of your app. This part you read.

Next: Vibe code a CRUD app applies these patterns to real data operations, and vibe coding security covers the full checklist across every layer.

Related reading:

Frequently Asked Questions

Can AI build a secure login system? +

AI reliably builds working authentication and unreliably builds working authorization. Login, sessions, and protected routes usually come out fine. Checking that a logged-in user may access a specific record is the part that gets skipped.

Should I let AI write authentication from scratch? +

No. Use an established provider — Supabase Auth, Clerk, Auth.js, or Better Auth. Password hashing, session handling, and token rotation are solved problems where subtle mistakes are catastrophic and invisible.

What is the difference between authentication and authorization? +

Authentication answers 'who are you' — logging in. Authorization answers 'are you allowed to touch this specific thing'. AI tools handle the first well and the second badly, which produces apps where any logged-in user can read any other user's data.

How do I test if my auth is actually working? +

Create two accounts. Log in as the first and note a record ID. Log in as the second and request that ID directly via URL and via the API. If you get data back, you have the most common vibe coding vulnerability.

Tagged:

#vibe-coding #authentication #security #supabase #tutorial

Enjoyed this article?

Get more AI tool picks, coding tutorials, and no-code automation guides every week. No spam, ever.

Found this useful? Share it:

More in AI Coding Tools