Auth and Row Level Security

Authenticate users

Create a sign-in flow, understand sessions and JWTs, and configure redirects and email delivery without confusing identity with permission.

Supabase Auth checks who a user is and gives them a session. What the rest of the platform cares about is what that session carries: a JWT, a signed token with claims about the user. The most important claim is the user ID. PostgreSQL policies read it later to decide what that user may do.

Sign a user up with the client library:

const { data, error } = await supabase.auth.signUp({
  email: 'ada@example.com',
  password: 'correct-horse-battery-staple',
})

New signups need email confirmation by default. On the local CLI stack no real email goes out. Messages land in a local Mailpit inbox instead. Run supabase status to get its URL, open the confirmation email there, and click the link. No rate limit, no real delivery, no waiting.

Then sign in and look at the identity you got back:

const { data, error } = await supabase.auth.signInWithPassword({
  email: 'ada@example.com',
  password: 'correct-horse-battery-staple',
})

console.log(data.user.id)
// 8f7a2c1e-4b9d-4e0f-a1c2-3d4e5f6a7b8c

That UUID is the value auth.uid() returns inside your policies. Create two local users now, Ada and Grace, and write down both IDs. Every authorization test in the lessons ahead compares what those two identities can and cannot do.

Identity is not permission

Authentication answers “who is this”. It does not decide which note, file, or channel a user may touch. That is authorization, and it lives in Row Level Security policies, not in Auth settings. Signing in gives Ada an identity. It gives her zero rows until a policy says otherwise.

The production checklist

Before real users arrive, configure three things on purpose: allowed redirect URLs, token handling, and email delivery.

Redirect URLs are an allowlist. A confirmation link pointing anywhere unlisted fails. That is a protection, not a bug, so add your production and preview domains before you test them.

Email is where hosted projects bite. Every project ships with a built-in mail service so auth flows work right away. But it is rate limited to a handful of messages per hour and meant for trying the flow, not for production. Iterate on a signup form against a hosted project and you will hit this:

Error: email rate limit exceeded

The frustrating part is how it shows up. Signups appear to succeed, confirmation emails just stop arriving, and only the error response says why. The fix is custom SMTP under the Authentication settings. Any transactional email provider works, with a sender address on a domain you verified there. After that, the rate limit is yours to raise in the dashboard.

My advice is to set up custom SMTP the same day you create the hosted project. It takes ten minutes, and it saves you from debugging “emails don’t arrive” on launch day.

Lesson completed