RowShield
Guides

Row Level Security alongside an external auth provider

Bringing your own authentication provider changes who mints identities, and nothing else. Once a request reaches PostgREST, authorization is decided by the same Row Level Security policies as always — the provider's badges and tiers mean nothing to Postgres, which sees a caller, a role, and a set of policies. The integration question is therefore narrow: how does an externally minted identity become something a policy can compare against?

Two architectures answer it, and this page covers both plus the traps each adds. It is hand-written guidance: RowShield does not automate provider integration and integrates with no external provider, though every catalog rule it ships applies unchanged no matter who signs your tokens.

RowShield does not detect this yet. This guide gives you the catalog queries to check it yourself. The nine rules that do ship are listed on the rules index.

Shape one: mirror identities into your database

The portable approach keeps Supabase auth unused and mirrors external users into a local table keyed by the provider's stable subject identifier. Your server verifies the provider token, resolves or provisions the matching local row, and every business table carries a user_id column referencing it. Policies then look entirely conventional — ownership comparisons against the mirrored identity, membership checks through join tables — and nothing about the database knows or cares that identity originated elsewhere.

The mirror earns its ubiquity through independence: it works with any provider, supports composite application-specific notions of identity, and keeps the schema authoritative about its own users. Its cost is synchronisation — provisioning on first sight, deactivation on removal, and the discipline of treating the provider as the source of truth while the database holds the working copy.

CREATE TABLE public.app_users (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  auth_provider text NOT NULL,
  provider_subject text NOT NULL,
  UNIQUE (auth_provider, provider_subject)
);

CREATE POLICY "docs_read_own"
  ON public.documents
  FOR SELECT
  TO authenticated
  USING (
    EXISTS (
      SELECT 1 FROM public.app_users u
      WHERE u.id = documents.owner_id
        AND u.provider_subject =
            (SELECT auth.jwt() ->> 'sub')
    )
  );

Shape two: delegate verification to Supabase

Recent Supabase builds can additionally be configured to verify tokens issued directly by selected external providers, accepting their signatures at the gateway so the token's subject claim flows through to auth.jwt() without a Supabase-issued session. Where your provider is supported, this removes the mirroring layer for pure authorization purposes: policies read the subject exactly as in the example above, minus the local lookup.

Treat support as a configuration fact to verify rather than an assumption — provider coverage and setup steps live in Supabase's third-party auth documentation and change at the platform's pace, not this page's. Where verification cannot be delegated, shape one remains universally available, which is why most production systems end up mirror-based regardless.

Traps this arrangement adds

Three failure modes recur. Type mismatch: provider subjects are strings, developer instincts reach for uuid columns, and comparing text against uuid forces casts that abandon indexes and slow every policy evaluation — store subjects as text, or index the cast expression deliberately. Silent emptiness: an unmapped or deactivated identity yields zero matching rows rather than an error, so deprovisioning bugs masquerade as empty dashboards instead of exceptions; log-and-alert on unexpected emptiness for authenticated users. Revocation lag: disabling a user at the provider does not instantly invalidate tokens already issued, so window-based reasoning applies — short token lifetimes bound the gap, and high-value actions deserve a fresh verification server-side regardless of what the database would admit.

-- Index the expression the policy actually compares,
-- when the subject must live as text beside uuid ids:
CREATE INDEX CONCURRENTLY idx_documents_owner_text
  ON public.documents ((owner_id::text));

What monitoring covers when auth is external

Everything RowShield automates operates downstream of identity minting and applies verbatim: tables shipping without RLS, policies that evaluate to true for everyone, writes missing WITH CHECK, predicates lacking indexes, unwrapped auth calls. External providers change who vouches for a token; they change nothing about what an unpoliced table exposes to the anon key. If anything, third-party setups warrant more vigilance, since the integration layer adds moving parts no provider audits for you.

The integration guidance above is manual and unautomated, as stated at the top. RowShield monitors the database posture itself on every scheduled scan, reads pg_catalog metadata only, and is an independent product, not affiliated with or endorsed by Supabase. Run a free audit at rowshield.dev/audit — a project URL is the only input.

Frequently asked

Do I still need RLS if my provider handles authentication?
Yes, without exception. Authentication establishes who a caller claims to be; Row Level Security is what limits what that caller may touch. A provider-verified identity facing unpoliced tables meets the same exposure as no authentication at all.
My policies return empty rows for valid users. Why?
Almost always a subject mismatch: the token's sub claim differing from the stored identifier by type, case or provider namespace, so the comparison matches nothing. Log both values for a failing user and the discrepancy is usually visible at a glance.
Where should the provider subject be stored?
In its native text form, in a dedicated column, uniquely indexed — either directly on business tables or in a mirror table joined by a uuid foreign key. Casting text subjects to uuid for comparison defeats indexes; storing text natively keeps policy evaluation on indexed seeks.

Check your project in about ten seconds

Paste a URL. No signup, no writes, nothing stored.

Run the free audit
supabase rls third party authsupabase external jwt rlssupabase clerk auth0 policies