12. Row-Level Security (RLS)

Alex needs each law firm employee to only see their own cases.

Row-Level Security lets you control which rows each user can read, insert, update, or delete. Policies are written in SQL and enforced by PostgreSQL itself — no application code needed.

Auth helper functions

Eurobase provides built-in functions you can use in RLS policies to access the current user's identity. Both the native (no-dot) and Supabase-style (auth.*) forms are equivalent — pick whichever reads better. is_service_role() is true for service-key calls and lets you write a single policy that admits both end-users and trusted server-side code.

FunctionReturnsDescription
auth_uid() / auth.uid()uuidCurrent user's ID, NULL when no end-user context
auth_email() / auth.email()textCurrent user's email
auth_role() / auth.role()text'service_role', 'authenticated', or 'anon'
is_service_role()booleanTrue for calls made with the service key (bypasses end-user RLS)
auth.jwt()jsonb{ sub, email, role } — for policies that read JWT claims

Heads up — do not redefine auth.uid() in your migrations. The Supabase boilerplate that reads request.jwt.claims won't work here — Eurobase uses a different session GUC. The built-in auth.uid() already does the right thing.

Common RLS patterns

Users can only read their own rows

CREATE POLICY "read own" ON todos FOR SELECT USING (user_id = auth_uid());

Users can insert with their own ID

CREATE POLICY "insert own" ON todos FOR INSERT WITH CHECK (user_id = auth_uid());

Users can update only their own rows

CREATE POLICY "update own" ON todos FOR UPDATE USING (user_id = auth_uid());

Public read, authenticated write

CREATE POLICY "public read" ON posts FOR SELECT USING (true);
CREATE POLICY "auth insert" ON posts FOR INSERT WITH CHECK (auth_role() = 'authenticated');

Admin access by email

CREATE POLICY "admin all" ON users FOR ALL USING (auth_email() = 'admin@company.eu');

Full example: secure a tasks table

Follow these steps to create a table where each user can only see and manage their own rows.

Step 1: Create the table with a user_id column

CREATE TABLE tasks (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  user_id UUID NOT NULL REFERENCES users(id),
  title TEXT NOT NULL,
  completed BOOLEAN DEFAULT false,
  created_at TIMESTAMPTZ DEFAULT now()
);

Step 2: RLS is enabled automatically

Tables created via the Eurobase console or API have RLS enabled by default. You'll see a green RLS badge on protected tables. If a table shows "RLS OFF" in the sidebar, enable it with:

ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;

Step 3: Add policies for each operation

-- Users can read only their own tasks
CREATE POLICY "select own" ON tasks FOR SELECT
  USING (user_id = auth_uid());
-- Users can insert tasks with their own user_id
CREATE POLICY "insert own" ON tasks FOR INSERT
  WITH CHECK (user_id = auth_uid());
-- Users can update only their own tasks
CREATE POLICY "update own" ON tasks FOR UPDATE
  USING (user_id = auth_uid());
-- Users can delete only their own tasks
CREATE POLICY "delete own" ON tasks FOR DELETE
  USING (user_id = auth_uid());

Step 4: Test it from the SDK

// Sign in as a user
await eb.auth.signIn({ email: 'alice@example.com', password: '...' })
// Insert a task — user_id is automatically checked by RLS
await eb.db.from('tasks').insert({ user_id: session.user.id, title: 'Buy milk' })
// Query — only Alice's tasks are returned
const { data } = await eb.db.from('tasks').select('*')
// data = [{ title: "Buy milk", ... }] — Bob's tasks are invisible

RLS is on by default for tables created via the console. But without policies, no rows are visible. Add at least a SELECT policy so users can read data. Tables showing "RLS OFF" in the sidebar need to be secured with ALTER TABLE ... ENABLE ROW LEVEL SECURITY;

Supabase compatibility: Eurobase's auth_uid(), auth_role(), and auth_email() follow the same pattern as Supabase's GoTrue. RLS policies written for Supabase work in Eurobase with minimal changes.

Secret API key (eb_sk_) bypasses RLS entirely — use it for server-side admin access, never in client code.