Security & Row-Level Security

basefyio uses a layered security model: API keys authenticate requests, JWT tokens identify users, and database Row-Level Security (RLS) policies control data access at the database level.

Authentication Flow

Client → API Key (apikey header) → Platform API
  │
  ├── Anonymous access (anon key, no JWT)
  │   └── database role: anon
  │
  └── Authenticated access (anon key + JWT Bearer)
      └── database role: authenticated
          └── RLS policies filter rows based on auth.uid()

Server → Service Key (apikey header)
  └── database role: service_role (bypasses RLS)

API Keys

KeyUse InRLSAccess Level
Anon KeyClient-side (browser, mobile)EnforcedOnly rows allowed by RLS policies
Service KeyServer-side onlyBypassedFull access to all data

Never expose your service key in client-side code, public repositories, or browser DevTools.

JWT Tokens

When a user signs in, basefyio issues a JWT containing their user ID, email, and roles. The JWT is verified against the project's auth realm JWKS endpoint. Claims are exposed to RLS policies via helper functions.

FunctionReturnsExample Usage
auth.uid()Current user ID (from JWT sub)WHERE user_id = auth.uid()
auth.jwt()Full JWT claims as JSONWHERE auth.jwt()->>'email' = email
auth.role()Current role nameWHERE auth.role() = 'authenticated'
auth.email()Current user emailWHERE owner_email = auth.email()

Row-Level Security (RLS)

Every project database has RLS enabled with three pre-installed roles:

  • anon — Unauthenticated users (public access)
  • authenticated — Signed-in users (JWT verified)
  • service_role — Server-side admin (bypasses all RLS)

The API automatically switches to the correct role based on the request's authentication context using SET LOCAL ROLE inside a transaction.

Writing RLS Policies

Use the SQL Editor in the dashboard to create policies:

-- Users can only read their own data
CREATE POLICY "Users read own data" ON profiles
  FOR SELECT
  TO authenticated
  USING (user_id = auth.uid());

-- Users can insert their own profile
CREATE POLICY "Users insert own profile" ON profiles
  FOR INSERT
  TO authenticated
  WITH CHECK (user_id = auth.uid());

-- Users can update their own profile
CREATE POLICY "Users update own data" ON profiles
  FOR UPDATE
  TO authenticated
  USING (user_id = auth.uid())
  WITH CHECK (user_id = auth.uid());

-- Public data readable by everyone
CREATE POLICY "Public posts readable" ON posts
  FOR SELECT
  TO anon, authenticated
  USING (published = true);

-- Only authors can edit their posts
CREATE POLICY "Authors edit own posts" ON posts
  FOR UPDATE
  TO authenticated
  USING (author_id = auth.uid());

Enable RLS on a Table

-- Enable RLS (required before policies take effect)
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;

-- Force RLS for table owner too (recommended)
ALTER TABLE profiles FORCE ROW LEVEL SECURITY;

Best Practices

  • Always enable RLS on tables that store user data. Without RLS, the anon role can read everything.
  • Use the anon key for clients. The service key should only be used in server-side code (backend APIs, cron jobs, admin scripts).
  • Test policies thoroughly. Use the SQL Editor with different roles to verify access.
  • Avoid storing secrets in the database. Use environment variables for API keys, tokens, and passwords.
  • Rotate compromised keys from the project settings. Regenerate the anon or service key if you suspect a leak.
  • Use HTTPS. basefyio enforces HTTPS in production via Traefik with automatic Let's Encrypt certificates.

Data Engine Security

The Data Engine enforces tenant isolation differently from RLS:

  • Every query has _projectId injected server-side as a mandatory filter
  • This filter cannot be omitted or overridden by callers
  • Documents from Project A are invisible to Project B, even in shared storage
  • The Data Engine uses the same JWT/API key authentication as the REST API

Audit Logging

basefyio logs security-relevant events:

  • SQL Audit Log — Every SQL query executed, with user, duration, and result count
  • Project Activity Log — Table/column/row changes, auth config changes, imports, exports
  • Data Engine Outbox — Every document create/update/delete event
  • Root Audit Log — Platform-level actions (user management, team changes, billing)