Skip to content
SPEN10
All posts

Security Basics for AI-Built Apps: Secrets, Auth and RLS

AI can build your app fast, but it won't always build it safely. The security basics every AI-built app needs: secret keys, logins and database rules.

8 min read

A padlock and chain locking two green wooden doors together
Photo by Leeroy on StockSnap

AI coding assistants are great at making things work. They're less reliable at making things safe, mostly because "safe" is invisible. An app with a security hole looks exactly like one without it, right up until someone finds the hole.

You don't need to become a security expert to build with AI. But there are a few basics every app needs, and they're worth understanding well enough to check the AI's work. Here are the three I'd start with: secrets, logins and database rules.

One honest note before we start: this is the foundation, not the whole building. If your app handles payments, health information or anything else sensitive, get a professional review before you launch.

First, know where your code runs

Most security mistakes in AI-built apps come from one misunderstanding: not knowing which code is public. A typical web app has three parts:

Browser Server Database public: anyone can read it private: only your code with Row Level Security Publishable key: OK Secret key: never Secret keys live here Checks who's logged in Rules on every table Users see their own rows With the publishable key, the browser can reach the database directly. RLS is what stops it.
Anything in the browser is public. Secrets belong on the server, and the database needs its own rules.
  • The browser (sometimes called the client) runs code that's sent to every visitor. Anyone can open their browser's developer tools and read it. Treat everything here as public.
  • The server runs code that visitors never see. This is where secrets and sensitive logic belong.
  • The database stores your data. With services like Supabase, the browser can talk to the database directly, which is convenient, but it means the database needs its own rules.

Keep that picture in mind and the rest of this post makes a lot more sense.

1. Keep secrets secret

A secret is any key, token or password that gives access to something: your database's admin key, a payment provider's secret key, an email service's API key. If someone gets one, they can act as your app.

The rules:

  • Store secrets in environment variables, not in your code. In a Next.js project, that usually means a .env.local file on your computer, and the environment variable settings in your hosting dashboard (like Vercel) for production.
  • Never commit .env files to Git. Check that your .gitignore file lists them. Once a secret is pushed to GitHub, assume it's exposed.
  • Watch for NEXT_PUBLIC_. In Next.js, any environment variable whose name starts with NEXT_PUBLIC_ is bundled into the code that runs in the browser. That's fine for values meant to be public. It's a disaster for a secret key. If an AI assistant adds that prefix to a secret to "fix" an error, say no.
  • Know which keys are public. Supabase gives you two kinds of keys: a publishable key (older projects call it the anon key) that's designed to be used in the browser, and a secret key (or service role key) that bypasses all your database rules. The publishable key is only safe because of the rules in section 3. The secret key must only ever live on the server.

If a secret does leak, deleting it from your code isn't enough. It still exists in your Git history and anywhere it was copied. Rotate it: generate a new key in the provider's dashboard and revoke the old one.

2. Don't build your own login system

Logins (authentication, or "auth") are surprisingly hard to get right: password hashing, password resets, sessions, email verification. This is one place where you shouldn't let an AI write it from scratch.

Use an established auth provider instead. Supabase Auth, Clerk and Auth0 are popular options, and your AI assistant will know how to wire them up.

Then remember the most common auth mistake: hiding a button is not security. If your app hides the "delete" button for people who aren't admins, but the code behind it doesn't check who's asking, anyone can still call it. Every action that changes or reveals private data should check who the user is on the server or in the database.

A set of keys hanging from the lock of a half-open door
Photo by WDnet Studio on StockSnap

3. Turn on row level security

Row Level Security (RLS) is a Postgres feature that Supabase relies on. It lets you write rules on each table that decide which rows a user can see or change. Without it, anyone with your publishable key, which is public, could read every row in a table that's exposed to the browser.

Here's what rules for an expenses table look like, letting each logged-in user see and add only their own rows:

alter table expenses enable row level security;

create policy "Users can read their own expenses"
on expenses for select
to authenticated
using ((select auth.uid()) = user_id);

create policy "Users can add their own expenses"
on expenses for insert
to authenticated
with check ((select auth.uid()) = user_id);

In plain English: turn on RLS for this table, then only let logged-in users read rows where the user_id matches their own ID, and only let them add rows with their own ID on them. Once RLS is on, anything without a matching rule is blocked. That's the safe default you want.

Two habits that make this stick:

  • Turn on RLS for every table, including ones you think don't matter. Supabase's dashboard has a security advisor that flags tables without it. Check it.
  • Test with two accounts. Sign up as two different users and make sure neither can see the other's data. It takes five minutes and catches most mistakes.

The Supabase guide walks through creating a table with RLS and testing the rules step by step. If you're still deciding on a database, start with why your AI-built app needs a database.

Ask your AI for a security review

Once your app works, ask your assistant to look at it with fresh eyes:

Review this project for security problems. Check for: secrets in the code
or in NEXT_PUBLIC_ variables, tables without row level security, actions
that don't check who the user is, and user input that isn't validated.
List what you find and explain each one before changing anything.

It won't catch everything, but it catches a lot, especially the obvious stuff that's easy to miss when you're excited to launch.

The short version

  • Anything in the browser is public
  • Secrets live in environment variables, on the server, never in Git
  • Use an established auth provider, and check permissions on the server
  • Turn on RLS for every table, and test with two accounts

None of this takes long. It's just easy to skip when the AI says "done!" and everything looks fine.

— Spencer

Share this post

Found this useful?

Get the next one in your inbox. No spam, unsubscribe anytime.