Module 6: Databases & Auth with Supabase
Lesson 5

Adding Authentication — Sign up, log in, user-specific data


Right now everyone who opens your app shares the same data. That's fine for a personal tool, but the moment a second person uses it, you need accounts. This lesson adds sign up, log in, log out, and — the real payoff — data that belongs to each individual user. Supabase Auth handles the genuinely hard parts (password hashing, sessions, email verification), so you mostly describe what you want and let Claude wire it up.


Why Authentication?

Authentication gives your app three things it can't have otherwise:

  • User accounts — sign up, log in, log out
  • Per-user data — your tasks are yours, not everyone's
  • Protected pages — visitors who aren't logged in get redirected to login

Supabase Auth manages password hashing, session tokens, email verification, and OAuth providers like Google. You never touch that machinery directly.


The Auth Flow

Here's the experience you're building:

User visits /  →  Not logged in?  →  Redirect to /login
                        ↓
                  User logs in / signs up
                        ↓
                  Redirect to home page
                        ↓
                User sees THEIR tasks only

That last line is where everything connects: the user_id column you added, the RLS policies you set, and the logged-in session all combine so each person sees only their own rows.


The Prompt: Add Authentication in One Pass

Hand this entire prompt to Claude Code:

Add authentication to my Next.js app using Supabase Auth.

Requirements:

  1. Create a /login page with:
    • Email + password sign in
    • A sign-up option (email + password)
    • A clean, centered form with good UX
    • Error messages for wrong credentials, an email that already exists, etc.
  2. Add a logout button to the navigation bar.
  3. Protect the home page:
    • If not logged in, redirect to /login
    • If logged in, show the page normally
  4. Make tasks user-specific:
    • When creating a task, set user_id to the current user's id (auth.uid())
    • When fetching tasks, only return rows where user_id equals the current user's id
  5. Create the auth middleware:
    • Add middleware.ts at the project root
    • Use Supabase's auth helpers to refresh sessions automatically

Tech: use @supabase/ssr for proper Next.js App Router authentication.

What Claude Will Create

After this prompt you'll have:

  • app/login/page.tsx — the login / sign-up page
  • middleware.ts — handles session refresh and redirects
  • Updates to your home page and nav component
  • Updated task queries that filter by the current user

Under the hood, the sign-up and sign-in calls look like this — Claude writes them, but recognize the names:

// Sign up a new user
await supabase.auth.signUp({ email, password })

// Log in an existing user
await supabase.auth.signInWithPassword({ email, password })

// Log out
await supabase.auth.signOut()

Getting the Current User

Throughout your app you'll often need to answer "who is logged in right now?" — for example, to stamp a new task with the right user_id. The pattern Claude uses:

// In a Server Component
const supabase = createClient()
const { data: { user } } = await supabase.auth.getUser()

// user.id    → their unique id
// user.email → their email address

Whenever a task needs an owner, this is where the user_id comes from.


Understanding the Session and Middleware

When a user logs in, Supabase creates a session — a token stored in the browser's cookies that proves they're authenticated.

On every page load, your middleware.ts:

  1. Checks for a valid session in the cookies
  2. If valid → allows access and passes user info to the app
  3. If not → redirects to /login

The middleware is also what refreshes the session automatically. Without it, sessions silently expire and users get logged out at random, frustrating moments. This is why the prompt above insists on creating it.


Defense in Depth: Why You Still Need RLS

It might feel like filtering tasks by user_id in your queries is enough. It isn't — and this is the most important security idea in the module.

  • Your app-level filter (only fetch where user_id = current user) is the first layer.
  • Row Level Security, the policies you set in the setup lesson, is the layer that cannot be bypassed. Even if your query forgets the filter, or someone calls the API directly with the public anon key, the database itself refuses to return rows that don't belong to them.

Two layers, and the database has the final say. (And the rule that never changes: the public anon key is the only key in the browser; the service_role key stays server-only and never lands in a NEXT_PUBLIC_* variable.)


Email Verification

By default, Supabase requires users to verify their email before logging in.

  • During development, you can turn this off to test faster: Supabase Dashboard → Authentication → Settings → Disable email confirmations.
  • For production, leave it on — it confirms users gave you real email addresses and cuts down on junk accounts.

Adding Google Login (Optional, but Nice)

Email/password works everywhere, but a "Continue with Google" button removes friction. In Supabase:

  1. Authentication → Providers → Google → enable it
  2. You'll need a Google OAuth Client ID and Secret

To get those:

  1. Go to console.cloud.google.com and create (or reuse) a project
  2. Create OAuth 2.0 credentials
  3. Add the authorized redirect URI: https://[your-project-id].supabase.co/auth/v1/callback

Then ask Claude:

Add a "Continue with Google" button to the login page using Supabase's signInWithOAuth method with the Google provider.


Testing Authentication

Walk through the full loop yourself:

  1. Go to http://localhost:3000/login
  2. Sign up with a test email and password → you should land on the home page
  3. Add a few tasks
  4. Log out → you should be sent to /login
  5. Log in again → your tasks should still be there

Then test user isolation — this is the one that proves your security works:

  1. Create a second account with a different email
  2. Log in as the second user
  3. Confirm you cannot see the first user's tasks

If user two can see user one's data, your RLS policies aren't doing their job — revisit the setup lesson before going any further.


Summary

  • Authentication adds user accounts and, crucially, per-user data.
  • Use the single full prompt to scaffold login, logout, page protection, user-specific queries, and middleware in one pass; the key calls are signUp, signInWithPassword, and signOut.
  • Get the current user with supabase.auth.getUser(); the middleware refreshes sessions so users don't get logged out unexpectedly.
  • Two layers of security: app-level filtering and Row Level Security — the database has the final say, and the service_role key never reaches the browser.
  • Test the full loop, then test user isolation: two accounts must never see each other's data.
  • Optional: add Google OAuth for easier sign-in.