Skip to main content

Row Level Security (RLS)

Row Level Security lets PostgreSQL enforce who can read and write each row — at the database level, not just in your application code.

This is critical for multi-tenant apps where multiple users store data in the same table, and it's a key requirement for NDPA 2023 compliance in fintech and healthtech apps.

Enable RLS on a table

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

Once RLS is enabled, no rows are returned by default until you add policies.

Create policies

Allow users to read only their own rows

CREATE POLICY "users see own orders"
ON orders
FOR SELECT
USING (user_id = auth.uid());

Allow users to insert only their own rows

CREATE POLICY "users insert own orders"
ON orders
FOR INSERT
WITH CHECK (user_id = auth.uid());

Allow users to update only their own rows

CREATE POLICY "users update own orders"
ON orders
FOR UPDATE
USING (user_id = auth.uid());

Allow public read (e.g. a product catalogue)

CREATE POLICY "products are public"
ON products
FOR SELECT
USING (true);

How NaijaBase enforces RLS

NaijaBase's REST API automatically sets the correct PostgreSQL role on every request:

Key typePostgreSQL roleRLS enforced
nb_anon_ keyanonYes
nb_service_ keypostgresNo (bypass)
JWT bearerauthenticatedYes

This means your RLS policies work automatically — you don't need to set any roles manually in your app. The proxy handles it transparently on every query.

Never expose your service key in frontend code.

Service role bypasses RLS

Your service_role key bypasses RLS entirely — this is intentional for admin operations. Never expose it in client-side code.

Example: multi-tenant SaaS

-- Create a profiles table tied to authenticated users
CREATE TABLE profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
company_name TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);

ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;

-- Each user sees only their own profile
CREATE POLICY "own profile only"
ON profiles
FOR ALL
USING (user_id = auth.uid())
WITH CHECK (user_id = auth.uid());

Test your policies

Use SET ROLE in psql to simulate a specific role without changing your connection user:

-- Test as the anon (unauthenticated) role
SET ROLE anon;
SELECT * FROM products; -- should only return rows allowed by anon policy

-- Test as an authenticated user
SET ROLE authenticated;
SET request.jwt.claims = '{"sub": "user-uuid-here", "role": "authenticated"}';
SELECT * FROM orders; -- should only return that user's orders

-- Return to your own role
RESET ROLE;

You can also check which policies exist on a table:

SELECT policyname, cmd, qual, with_check
FROM pg_policies
WHERE tablename = 'orders';

And verify RLS is enabled:

SELECT relname, relrowsecurity
FROM pg_class
WHERE relname = 'orders';

Why this matters for NDPA compliance

With RLS enabled, even if a bug exists in your application code, the database blocks unauthorized data access. For fintech and healthtech apps handling Nigerian personal data, this provides a second layer of protection that satisfies NDPA 2023 data security requirements.

See NDPA Compliance →