Supabase
Broken Authentication

Broken Authentication in Supabase Apps

Supabase Auth provides robust authentication, but developers frequently confuse authentication with authorization. Being logged in doesn't mean a user should access everything. The gap between these concepts creates critical vulnerabilities.

Scan Your Supabase App

How It Happens

Supabase Auth correctly handles user registration, login, and session management. The authentication layer works well. The breakdown happens at the authorization layer, where the app decides what each authenticated user can do. Developers often write RLS policies that only check if a user is authenticated (auth.role() = 'authenticated') without verifying the user should access that specific row. This means any logged-in user can read any other user's data. Another common pattern is implementing role-based access in the frontend JavaScript while the database allows any authenticated user full access. Admin panels protected only by React conditional rendering are trivially bypassed by calling the Supabase API directly. Custom auth flows that bypass Supabase Auth (like using a custom JWT without proper validation) can also introduce authentication vulnerabilities at the protocol level.

Impact

Broken authorization allows any authenticated user to access data belonging to other users. In a multi-user application, this means user A can read user B's private messages, files, settings, and any other personal data. Privilege escalation is common: a regular user can access admin functions if RLS policies only check authentication status rather than specific roles. This gives attackers control over the entire application. In SaaS applications, broken authorization leads to cross-tenant data leaks where one organization's data is accessible to users from other organizations. This often has legal and compliance implications beyond the technical vulnerability.

How to Detect

Create two separate user accounts. Log in as user A and note the data you can access. Then check if user B can access user A's data by querying the same tables with user B's token. Review RLS policies for conditions that only check auth.uid() IS NOT NULL (any authenticated user) rather than auth.uid() = user_id (the data owner). The former is the most common authorization mistake. Vibe App Scanner tests authentication boundaries by analyzing RLS policies and probing data isolation between different user contexts.

How to Fix

Rewrite RLS policies to check row ownership, not just authentication status. Every policy should include (select auth.uid()) = user_id or an equivalent ownership check. For role-based access, store roles in a protected table and check them in RLS policies. Use Supabase custom claims (JWT app_metadata) for performance-critical role checks that don't require a database lookup. Implement multi-tenant isolation by adding an organization_id column to all tables and including it in RLS policies. Users should only access rows belonging to their organization. Audit all RPC functions for proper authorization. Functions with SECURITY DEFINER run with elevated privileges and must implement their own access checks rather than relying on the caller's context.

Code Examples

RLS policy authorization check

Vulnerable
-- Only checks if user is logged in
CREATE POLICY "Authenticated users can read"
  ON messages FOR SELECT
  TO authenticated
  USING (true);  -- Any logged-in user sees ALL messages
Secure
-- Checks row ownership
CREATE POLICY "Users read own messages"
  ON messages FOR SELECT
  TO authenticated
  USING (
    (select auth.uid()) = sender_id
    OR (select auth.uid()) = receiver_id
  );

Frequently Asked Questions

What is the difference between authentication and authorization?

Authentication verifies who you are (login). Authorization determines what you can do (access control). Supabase Auth handles authentication well, but authorization must be implemented through RLS policies by the developer.

Is auth.role() = 'authenticated' a valid RLS check?

Only for data that should be accessible to all logged-in users. For user-specific data, you must check auth.uid() = user_id to ensure each user only sees their own rows.

How do I implement admin access in Supabase?

Store admin status in the user's app_metadata (set via service_role on the server) and check it in RLS policies: (select auth.jwt() ->> 'role') = 'admin'. Never store admin flags in user-editable tables without RLS protection.

Is Your App Vulnerable?

VAS automatically scans for broken authentication and other security issues in Supabase apps. Get actionable results with step-by-step fixes.

Scans from $5, results in minutes.