Development & Building with AI · Explainer
Supabase: authentication, database and access rules
With Supabase, the browser talks directly to the database. That is only safe if the access rules live in the database itself. See how the three pieces fit together.
- Author:
- Redação Token & Tech
- Responsible editor:
- Daniel Ferreira
- Translation:
- Redação Token & Tech
Published · 6 min read
Originally published in Brazilian Portuguese.
Translated from the Portuguese original with AI assistance.
Also available in: Português
Key points
- Every Supabase project is a full Postgres database, with login (Supabase Auth) and a REST API generated from your tables.
- Who can see and change each row is decided in the database, by Row Level Security policies that use the logged-in user's identity.
- The publishable key can go to the browser; the secret key bypasses every policy and must stay on the server only.
Many projects built with the help of AI, from prototypes to small products, use Supabase as their backend. The promise is attractive: database, login and API ready to go, without writing a server. The price of that convenience is a rule many people only discover late: data security now lives inside the database.
This article explains Supabase’s three main pieces (database, authentication and access rules) and how they talk to each other. It is based on the official Supabase and PostgreSQL documentation, consulted on October 8, 2026. Key names and features change often; check the documentation before configuring a project.
The database: a full Postgres
According to the documentation, every Supabase project gets “a full Postgres database, not a Postgres abstraction” (Supabase, Database). PostgreSQL (or Postgres) is an open-source relational database, used for decades in systems of every size.
In practice, this means you work with real tables, foreign keys, indexes and SQL. You can edit through the visual dashboard, the SQL editor or a direct connection. The same page lists extensions that can be enabled from the dashboard, such as pgvector (vector search, useful for RAG), PostGIS (geographic data) and pg_cron (scheduled jobs).
The database is also the foundation of Supabase’s other services, such as authentication, file storage and functions.
The API generated from your tables
Supabase creates a REST API from your database schema, so you can access data from the browser without writing server code (Supabase, Data REST API). This layer is PostgREST, described in the documentation as a thin layer on top of Postgres. When you change a table, the API reflects the change.
Here is the central point of this article. In a traditional system, the browser talks to a server of yours, and that server decides what each user can see. With Supabase, the browser talks almost directly to the database. If the database does not know who is asking and what that person is allowed to do, it hands over whatever is requested.
That is why the documentation says the API was configured to work with Postgres Row Level Security. Before getting to that, you need to understand how the database knows who the user is.
Authentication: Supabase Auth
Supabase Auth is the sign-up and login service. The documentation lists the supported methods: password, magic link and one-time code by email, social login (Google, GitHub, Apple and others), enterprise single sign-on (SSO, including SAML 2.0) and phone login through SMS providers (Supabase, Auth).
Users are stored in the project’s own database, in a separate schema called auth. You can link these records to your tables, for example with a profiles table whose key points to the user’s id.
When someone logs in, Auth issues a JWT (JSON Web Token): a digitally signed piece of text that carries data about the user. The signature makes it possible to verify that nobody has altered the content. According to the documentation, the most important fields are sub, the user’s unique id, and role, the Postgres role used when applying access policies (Supabase, JWT). These tokens are short-lived and are renewed while the person uses the system.
Supabase’s libraries send this token with every request. The database then knows who is asking. The documentation sums it up: the Auth token scopes database access row by row when used together with RLS policies.
Access rules: Row Level Security
Row Level Security (RLS) is a native PostgreSQL feature. It restricts, per user, which rows a query can return and which rows a command can insert, update or delete (PostgreSQL, Row Security Policies). Each rule is called a policy.
The Supabase documentation uses a helpful image: a policy works like a WHERE clause automatically added to every query on that table (Supabase, Row Level Security).
Three Postgres behaviors are important:
- Without RLS enabled, broad access applies. Before the feature is turned on, all rows are available to anyone with SQL permission on the table.
- RLS enabled with no policy blocks everything. According to the PostgreSQL manual, if no policy exists, a default-deny rule applies: no row is visible or can be modified.
- Policies add up. Ordinary (permissive) policies are combined with “or”; restrictive policies, with “and”.
A simple example
Imagine a notes table in which each person can only see and create their own notes. This is an illustrative example by the editorial team, following the pattern described in the documentation:
alter table notes enable row level security;
create policy "read own notes"
on notes for select
to authenticated
using ( (select auth.uid()) = owner_id );
create policy "create notes as self"
on notes for insert
to authenticated
with check ( (select auth.uid()) = owner_id );
What each part does:
auth.uid()returns the id of the user who made the request, taken from the JWT. Without login, it returns empty (null), and the comparison fails.to authenticatedlimits the policy to logged-in users. Supabase uses theanonrole for requests without login andauthenticatedfor requests with login.usingdefines which existing rows the command sees or can affect.with checkdefines what the new or modified row must look like. Without it in the insert policy, nothing would stop someone from saving a note with another person’s id.
The Supabase documentation recommends writing one policy per operation (select, insert, update, delete) and notes that an update only works as expected if there is also a corresponding select policy.
API keys: which one can go to the browser
Supabase works with two types of key (Supabase, API keys):
- Publishable key (
sb_publishable_..., successor to the oldanonkey). It has low privilege and, according to the documentation, can be exposed. Everything it can reach is limited by RLS. - Secret key (
sb_secret_..., successor to the oldservice_rolekey). The documentation is blunt: it bypasses all RLS policies. It is meant for server components only.
The consequence is that the publishable key in the site’s code is not, in itself, a leak. A leak happens when a table is exposed without RLS, or when the secret key shows up in the browser, in a repository or in a log. As of the date consulted, the documentation stated that the old anon and service_role keys are being phased out by the end of 2026 and recommended migrating to the new ones.
This concern connects with what we discussed in how to build web tools with AI without compromising security: AI-generated code tends to work on the first test and fail precisely on permissions.
Common mistakes and how to avoid them
Based on the official documentation’s recommendations, these are the points that need the most attention:
- A table without RLS in an exposed schema. The Supabase documentation states that every table in an exposed schema must have RLS enabled; without it, any role with permission can read and write.
- Thinking a policy removes permissions. Policies filter rows; permissions (grants) are checked separately. The documentation recommends revoking from
anonandauthenticatedthe permissions they do not need. - The secret key in the front end. Use it only on the server, one per component, and rotate it if it leaks.
- Forgetting the table owner. In PostgreSQL, superusers and roles with the
BYPASSRLSattribute always bypass policies, and so does the table owner normally, unlessFORCE ROW LEVEL SECURITYis used. Testing as an administrator does not prove the rule works for an ordinary user. - Slow policies. The documentation suggests creating indexes on the columns used in policies and wrapping
auth.uid()inselect, so that the value is computed once per query, not once per row. - Not testing denial. The documentation recommends testing, for each operation and role, both what should be allowed and what should be denied, with the pgTAP tool.
Key takeaways
- Supabase delivers a full Postgres with login and API ready to go. The API lets the browser talk almost directly to the database.
- That is why authorization lives in the database: the JWT says who the user is, and RLS policies say what that person can do on each row.
- Enable RLS on every exposed table, write policies per operation, revoke unnecessary permissions and keep the secret key out of the browser.
- Also test what should be blocked. An access rule is only ready once you have seen it deny.
Sources
- Database, Supabase (documentação oficial). Accessed October 8, 2026
- Auth, Supabase (documentação oficial). Accessed October 8, 2026
- JSON Web Token (JWT), Supabase (documentação oficial). Accessed October 8, 2026
- Data REST API, Supabase (documentação oficial). Accessed October 8, 2026
- API keys, Supabase (documentação oficial). Accessed October 8, 2026
- Row Level Security, Supabase (documentação oficial). Accessed October 8, 2026
- PostgreSQL 18 Documentation – 5.9. Row Security Policies, The PostgreSQL Global Development Group. Accessed October 8, 2026