Join our Newsletter — 33% off our NHI Course
Home› Guides› Supabase Keys Security Guide: Anon, Service Role and…
Guide Non-Human Identity (NHI)

Supabase Keys Security Guide: Anon, Service Role and Row Level Security

PDF for newsletter subscribers · free to read online

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 26 October 2026 13 min read
On this page

In Supabase the public key is meant to be public, so the only thing standing between an anonymous visitor and your tables is Row Level Security. Teams that treat the anon or publishable key as a secret, or that forget RLS on a single table, end up with a database that anyone can read and write through the auto-generated API. This guide explains the publishable and secret key model, the legacy anon and service_role JWTs being retired, how keys map to Postgres roles, and how to write, test and monitor RLS. It also covers the other credentials in a Supabase project: the postgres password, JWT signing keys and Management API tokens. It is for developers, platform engineers and NHI owners running Supabase in production.

Key takeaways

  • Publishable keys are not secrets. A sb_publishable_ key (or legacy anon JWT) ships in every browser and maps to the anon or authenticated Postgres role; RLS is what protects data.
  • Secret keys bypass every policy. A sb_secret_ key (or legacy service_role JWT) runs as service_role, which has the BYPASSRLS attribute, so it belongs only in backend code.
  • Move off the legacy JWT keys. Supabase is deprecating the long-lived anon and service_role JWTs by the end of 2026, and they stay valid until you deactivate them.
  • RLS has two layers. Postgres checks table grants before policies, and Supabase projects can grant every privilege on new public tables to anon and authenticated by default.
  • Test as an attacker would. Run the Security Advisor and pgTAP tests for each role and operation before release, and use one secret key per backend component so one leak means one rotation.

How identity and access work in Supabase

A Supabase project is a Postgres database fronted by APIs: PostgREST for tables, Auth, Storage, Realtime and Edge Functions. Each API request carries an API key in the apikey header and, if a user is signed in, that user's access token. The API gateway uses them to pick a Postgres role, and Postgres enforces grants and RLS policies for that role. The key therefore decides the starting role, not what data the request sees.

KeyFormatPostgres roleRLSWhere it may live
Publishable keysb_publishable_...anon, or authenticated with a user sessionEnforcedBrowsers, mobile apps, CLIs, source code
Secret keysb_secret_...service_roleBypassed when no user token is presentBackend services, workers, Edge Functions via @supabase/server
Legacy anon keyLong-lived JWT (eyJ...) signed with the legacy JWT secretanonEnforcedBeing deprecated; replace with publishable
Legacy service_role keyLong-lived JWT signed with the legacy JWT secretservice_roleBypassedBeing deprecated; replace with secret keys
User access tokenJWT issued by Supabase AuthauthenticatedEnforced, auth.uid() setClient session

Supabase documents several protections for secret keys: they are rejected with HTTP 401 when the request User-Agent looks like a browser, you can create several secret keys so each backend component has its own, and they are sent in the apikey header rather than Authorization: Bearer because they are not JWTs. The browser check is a guard rail, not a boundary: a secret key copied into a script still works. See the API Key Management Guide.

The other Postgres roles

  • postgres: the admin role whose password you set at project creation; dashboard SQL runs as this role and is tagged -- source: dashboard in logs.
  • authenticator: validates the JWT and switches to the role it names.
  • Service roles: supabase_auth_admin, supabase_storage_admin and supabase_admin are platform roles scoped to their schemas or tasks; do not reuse them.
  • Custom roles: Supabase recommends a separate login role per service connecting directly to Postgres instead of sharing postgres. A custom role given BYPASSRLS is as sensitive as a secret key.

Row Level Security done properly

Once RLS is enabled on a table, no rows are returned to publishable key requests until you add policies, so the safe default is closed. Missing RLS is a broken object level authorisation flaw by another name. See the OWASP API Top 10 API1 Broken Object Level Authorization Guide.

  1. Enable RLS in migrations: alter table public.reports enable row level security; in a versioned migration, so every environment matches.
  2. Fix grants first: revoke default privileges from anon, authenticated and service_role on tables in exposed schemas and grant back only what each role needs. A missing grant fails with error 42501 before any policy runs, which helps debugging.
  3. Write one policy per operation: separate select, insert, update and delete policies, scoped with TO authenticated or TO anon. using filters rows that can be seen or touched; with check validates new or changed rows.
  4. Check identity explicitly: auth.uid() returns null for unauthenticated requests, so add auth.uid() is not null, and wrap it as (select auth.uid()) so Postgres evaluates it once per statement.
  5. Take authorisation claims from app metadata: read roles and tenant IDs from raw_app_meta_data via auth.jwt(); users can edit raw_user_meta_data. Claims stay stale until the token refreshes.
  6. Close the bypass routes: views bypass RLS by default, so create them with (security_invoker = true) on Postgres 15 and later. security definer functions run with their creator's rights: set search_path = '', schema-qualify names and keep them out of exposed schemas.
  7. Index policy columns: a policy acts like a WHERE clause on every query, so index the columns it filters on.
  8. Test allow and deny cases: write pgTAP tests in supabase/tests/ and run supabase test db for every role and operation in CI.

Common policy patterns

NeedPolicy approachPitfall
Users see only their own rowsusing ((select auth.uid()) = user_id) for select, update, delete; with check on insertForgetting with check lets users insert rows owned by others
Multi-tenant SaaSCompare a tenant column to a claim from raw_app_meta_data or a membership tableTrusting user-editable metadata
Public read, owner writeselect policy TO anon, authenticated using (true); write policies tied to auth.uid()A single for all using (true) policy opens writes too
Admin back officeServer-side with a dedicated secret key, or an authenticated policy keyed to an app metadata roleShipping the secret key in an admin SPA
Anonymous sign-in usersTreat them as authenticated with an is_anonymous claim check where neededConfusing the anon role with anonymous users

Admin roles and privileged access

Dashboard access is a credential plane of its own: anyone who can open the project can read the secret keys and change the database password.

Organisation roleAccessTreat as
OwnerFull access to the organisation and every projectTier 0; at least two named owners, MFA
AdministratorFull access except organisation settings, transferring projects out and adding ownersPrivileged
DeveloperRead-only on organisation resources, content access to projects, no project settingsData access; can usually see data and keys used in development
Read-OnlyRead-only on organisation and project resourcesStandard, review quarterly
No-access plus project-scoped roleAccess only to assigned projects (Team and Enterprise plans)Use to separate production from development
  • Enforce MFA: the Supabase production checklist asks for MFA on every account (and 2FA on GitHub where GitHub sign-in is used), suggests organisation-wide MFA enforcement, and more than one owner.
  • Use SSO where available: invites from a SAML SSO account can only be accepted by an account from the same identity provider, which stops invitations to unmanaged accounts.
  • Split production: give developers project-scoped roles on development projects and keep production to a small group.

Integrations, API tokens and service accounts

Beyond the project keys, three more credential types reach a Supabase project. See the Secrets Management Guide.

CredentialGrantsLifetime and controlsPractice
Management API personal access token, scopedSelected organisations, projects and permissionsCustom expiry; shares a 120 requests per minute limit per userUse for CI and automation; one per pipeline
Classic personal access token (shown as Legacy)Same privileges as the user accountCustom expiryReplace with scoped tokens
OAuth2 token for a Supabase integrationScopes the user approvedShort-livedReview connected apps; revoke unused
postgres or custom role passwordDirect Postgres or pooler connectionStatic; changing it causes no downtime for Supabase servicesVault it; per-service roles; rotate after staff changes
JWT signing keySigns user access tokens (and, for the legacy secret, the legacy keys)Asymmetric RS256 or ES256 recommended; HS256 not recommended for productionRotate through standby, current, previously used, revoked

Migrating from legacy keys and the JWT secret

  1. Create new keys: generate a publishable key and one secret key per backend component in Settings > API Keys.
  2. Replace in code: swap the legacy anon key for the publishable key in clients and the service_role key for the right secret key in each service. In Edge Functions use the @supabase/server SDK rather than reading keys from environment variables.
  3. Move to asymmetric signing keys: add an ES256 (P-256) or RS256 key as standby, then rotate it to current. Verifiers can fetch public keys from /auth/v1/.well-known/jwks.json, which may be cached for up to 20 minutes combined.
  4. Deactivate the legacy keys: watch API gateway logs until no requests use them, then deactivate. Deactivation is reversible, which is your rollback.
  5. Revoke the legacy JWT secret: only after anon and service_role are disabled, and, with one hour access tokens, at least 1 hour 15 minutes after rotation unless you are mid-incident.

Rotating a leaked secret key

Create a replacement secret key, deploy it to the affected component, confirm the old key no longer appears in logs, then delete the old key. Deletion is permanent, so do it last. Because each component holds its own key, the others keep running. Never log keys: if you must, keep no more than six characters after the prefix or a SHA-256 hash.

Logging and detection

SourceRecordsNotes
Logs Explorer (API gateway, PostgREST, Postgres, Auth, Storage, Realtime, Edge Functions, pooler)Requests, status, method, path, user, database eventsRetention depends on plan; export continuously with log drains
Platform Audit LogsDashboard and Management API actions by members: actor IP, email, token type, action, targetTeam and Enterprise only; stream with audit log drains
Auth audit logsSign-in and account events inside the projectSeparate from platform logs
Security AdvisorLints such as 0013 RLS disabled in public, 0007 policy exists RLS disabled, 0010 security definer view, 0002 auth users exposed, 0011 function search path mutableRun in Studio, supabase db advisors or the Management API

Detections worth building

  • Bulk reads with the publishable key: large or paginated select responses from the anon role to sensitive tables (T1213 Data from Information Repositories).
  • Secret key use from unexpected sources: service_role requests from new IPs or user agents (T1078.004 Valid Accounts: Cloud Accounts).
  • Key and setting changes: new secret keys, deactivated keys, signing key rotation, network restriction changes and member invitations in Platform Audit Logs.
  • Advisor regressions: fail CI when a migration produces a new RLS or security definer lint.
  • Leaked keys: scan repositories, client bundles and logs for sb_secret_ and service_role JWTs (T1552.001 Credentials In Files).

Metrics

  • RLS coverage: percentage of tables in exposed schemas with RLS enabled and at least one policy. Target 100%.
  • Advisor findings: open security lints per project, with time to resolve.
  • Legacy key retirement: projects still accepting legacy anon or service_role keys. Target zero before Supabase's deprecation.
  • Secret key sprawl: secret keys per project with a named owner and component; target one per component, none unowned.
  • Policy test coverage: percentage of tables with pgTAP allow and deny tests in CI.

Hardening baseline

  • Data API: RLS on every table, default grants revoked, views with security_invoker, no security definer functions in exposed schemas.
  • Database connections: enable SSL enforcement and network restrictions. Restrictions cover Postgres and the pooler only, not PostgREST, Auth or Storage, and Edge Functions then lose direct database access. Manage them with supabase network-restrictions update --db-allow-cidr, remembering that update replaces the list unless you add --append.
  • Auth: email confirmations on, OTP expiry at 3,600 seconds or less, custom SMTP, user MFA where risk warrants it.
  • Account: MFA for all members, two or more owners, scoped PATs only.
  • Keys: publishable plus per-component secret keys, legacy keys deactivated, asymmetric JWT signing.

For small teams on a single project, the order that pays off fastest is RLS and advisors, then secret key separation, then account MFA. Larger organisations should add project-scoped roles, Platform Audit Logs with drains and a policy test gate in CI.

Common misconfigurations

  • RLS off on a table behind a public key: the Moltbook database exposure combined a publishable key in client JavaScript with no RLS, giving anyone read and write access to tables holding about 1.5 million AI agent API tokens. The key was working as designed; the missing policies were the flaw.
  • Service role key in the frontend: often introduced to "fix" a permissions error, it removes every policy for every visitor.
  • Policy without with check: users can update a row to belong to someone else or insert rows under another user ID.
  • Authorisation from user_metadata: users can set their own role or tenant claim.
  • Views and security definer functions in public: both bypass RLS unless configured otherwise.
  • One shared secret key everywhere: a leak from a single worker forces rotation across every service, so teams delay it.

Framework mapping

Control areaNIST SP 800-53 Rev 5NIST CSF 2.0CIS Controls v8.1ISO/IEC 27001:2022 Annex A
Key and token lifecycleIA-5, AC-2PR.AA-015 Account ManagementA.5.17
RLS and least privilegeAC-3, AC-6PR.AA-056 Access Control Management, 3 Data ProtectionA.5.15, A.8.3
Network restrictions and SSLSC-7, SC-8PR.IR-01, PR.DS-0212 Network Infrastructure ManagementA.8.20, A.8.24
Admin rolesAC-6PR.AA-056 Access Control ManagementA.8.2
Logging and reviewAU-2, AU-6DE.CM-038 Audit Log ManagementA.8.15, A.8.16

Practitioner checklist

  • Enable RLS on every table in exposed schemas through migrations and revoke default grants.
  • Write separate policies per operation with using and with check, and an explicit auth.uid() is not null.
  • Run the Security Advisor and pgTAP policy tests in CI and fail builds on new findings.
  • Replace legacy anon and service_role JWTs with publishable and per-component secret keys, then deactivate the legacy keys.
  • Move to asymmetric JWT signing keys and revoke the legacy JWT secret.
  • Keep secret keys out of browsers, bundles, URLs, logs and repositories; scan for sb_secret_ values.
  • Enforce MFA for all members, keep two or more owners and use project-scoped roles for production.
  • Use scoped personal access tokens with expiry for automation; retire classic tokens.
  • Enable SSL enforcement and network restrictions, and drain logs and Platform Audit Logs to your SIEM.

Standards and references

Related NHI Mgmt Group resources: API Key Management Guide · Firebase Credentials Security Guide · MongoDB Atlas Credentials Security Guide · Database Credential and Access Security Guide · Secrets Management Guide

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 26 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.