SaaS User Authentication: Sessions, JWTs, Supabase Auth & Row Level Security

Auth Fundamentals

Before building anything, you need to understand two concepts that sound similar but are very different:

You need both. Authentication without authorization means every logged-in user can do everything. Authorization without authentication means you can't tie permissions to anyone. A secure SaaS application implements both layers working together.

Sessions vs JWTs

There are two dominant approaches to managing authenticated state:

Session-Based Authentication

The server creates a session and stores it (in memory, a database, or Redis). The browser receives a session ID cookie. On every request, the server looks up the session to identify the user.

Pros:

Cons:

JWT-Based Authentication

The server issues a signed JSON Web Token containing user info. The client stores it (usually in a cookie or localStorage). On every request, the server verifies the signature — no lookup needed.

Pros:

Cons:

Supabase uses JWTs under the hood but manages the complexity for you — token refresh, secure storage, and expiry are all handled automatically.

Supabase Auth Setup

Supabase gives you a complete authentication system out of the box:

Setup in 4 Steps

  1. Install the Supabase client — add the SDK to your project and initialize with your project URL and anon key.
  2. Enable providers — turn on email/password and any OAuth providers in the Supabase dashboard.
  3. Create auth forms — build signup and login forms that call supabase.auth.signUp() and supabase.auth.signInWithPassword().
  4. Check auth state — use supabase.auth.getSession() to determine if a user is logged in and access their data.

Signup, Login, and Logout Flows

Signup

Keep your signup form minimal — email and password is enough to start. Decide early whether you require email verification before granting access. For most SaaS products, verify email but let users explore the product immediately.

Login

Always use generic error messages. Say "Invalid email or password" — never "No account found with this email" (that leaks which emails are registered). Include a "Forgot password" link that triggers a password reset email.

Logout

Call supabase.auth.signOut(), redirect the user to your landing page or login screen, and clear any cached user data from local state. Don't forget to clean up any in-memory state or context providers.

The Protected Routes Pattern

A protected route is a wrapper component that checks for an active session before rendering its children. If no session exists, the user is redirected to the login page.

Your wrapper needs to handle three states:

  1. Loading — auth state is being checked (show a spinner or skeleton).
  2. Authenticated — session exists, render the protected content.
  3. Unauthenticated — no session, redirect to login.

A key UX detail: store the user's intended destination before redirecting to login. After successful authentication, send them back to where they were trying to go instead of always dumping them on the dashboard.

Row Level Security (RLS)

Row Level Security is database-level access control built into PostgreSQL and fully supported by Supabase. It's the most important security feature you'll implement.

Without RLS: Any authenticated user could query any other user's data by guessing or enumerating IDs. Your API is only as secure as your frontend code — which means it's not secure at all.

With RLS: The database automatically filters every query so users can only access rows they're authorized to see. Even if someone crafts a malicious API request, the database enforces the rules.

Rules to Follow

RLS is not optional. It's the difference between a secure application and a data breach waiting to happen.

Common Auth Mistakes

These are the mistakes that show up repeatedly in early-stage SaaS products:

  1. Storing passwords in plain text — always hash passwords. Supabase Auth handles this for you, but if you ever roll your own, use bcrypt or argon2.
  2. Client-side only auth checks — checking auth in the browser is for UX (hiding UI elements). Security must be enforced server-side or at the database level with RLS.
  3. Not enabling RLS — if you skip RLS, any user with a valid JWT can read or modify any row in your database.
  4. Exposing sensitive info in error messages — never reveal whether an email exists, what database column failed, or internal error details. Log those server-side; show generic messages to users.
  5. Skipping HTTPS — without HTTPS, credentials are sent in plain text over the network. Use HTTPS everywhere, even in development (most hosting providers enforce this by default).