Security & Row Level Security
Users, Roles, Privileges, Grants, Revokes — and the crown jewel of multi-tenant SaaS security: Row Level Security (RLS).
Users
Creating and managing database users (login accounts)
Users vs Roles — The Secret
In PostgreSQL, users and roles are the same thing. A user
is simply a role that has the LOGIN privilege. Both are stored in
pg_roles. This unified model is more flexible than traditional
user/group systems.
Creating Users
Viewing Users
Roles
Grouping privileges with roles for clean, maintainable access control
What is a Role?
A role (without LOGIN) is a named bundle of permissions. Instead of granting individual privileges to each user, you grant them to a role, then add users to that role. This is the PostgreSQL equivalent of "groups" in other systems.
Creating and Using Roles
Role Inheritance
Privileges
What users can and cannot do — the complete privilege system
Privilege Levels
PostgreSQL has a layered privilege system. You need to grant access at each level:
Table Privileges Reference
| Privilege | Allows | Example use |
|---|---|---|
SELECT | Read rows | Analytics, reporting users |
INSERT | Add new rows | API service writing data |
UPDATE | Modify existing rows | App service updating records |
DELETE | Remove rows | Cleanup jobs |
TRUNCATE | Empty entire table fast | Admin/maintenance scripts |
REFERENCES | Create foreign keys referencing this table | Schema management |
TRIGGER | Create triggers on the table | Admin only |
ALL PRIVILEGES | Everything above | Owner / admin role |
Default Privileges Problem
SELECT ON ALL TABLES only covers existing tables.
New tables created later will NOT be covered. Use ALTER DEFAULT PRIVILEGES to fix this.
Grants
Giving access — the complete GRANT syntax
Full Grant Examples
Complete Setup for an App User
Revokes
Taking access away — and the public schema trap
REVOKE Syntax
The Public Schema Security Trap
CREATE and USAGE on the public schema.
Any logged-in user can create tables! Lock this down immediately in production.
Audit Who Has What Privilege
Row Level Security (RLS)
The most powerful security feature — automatically filter rows per user
What is RLS?
Row Level Security lets you attach a WHERE clause to a table
that automatically applies to every query — based on who is running the query. Users
literally cannot see rows they don't have access to, even if they run
SELECT * FROM orders.
Step 1 — Enable RLS on a Table
Step 2 — Create Policies
A policy is the WHERE clause that gets added to queries. You can have different policies for SELECT, INSERT, UPDATE, DELETE.
USING vs WITH CHECK
| Clause | Used For | What It Checks |
|---|---|---|
USING (expr) | SELECT, UPDATE, DELETE | Filters EXISTING rows — rows not matching are invisible |
WITH CHECK (expr) | INSERT, UPDATE | Validates NEW/modified rows — prevents inserting to wrong tenant |
Managing Policies
RLS — Multi-Tenant SaaS Pattern
Complete end-to-end example: isolating data between organizations
The Architecture
Complete Multi-Tenant Setup
Step 5 — App Code Sets the Context
Your backend (FastAPI/Node) sets the tenant context at the start of every request, before any query runs:
Python / FastAPI Integration Pattern
Advanced: User-Level Policies Within a Tenant
Security Best Practices
Production-ready security checklist for PostgreSQL
Production Security Checklist
REVOKE CREATE ON SCHEMA public FROM PUBLIC;readonly / readwrite roles, then assign users to roles.CREATE USER app_service CONNECTION LIMIT 50 — prevent runaway connections from crashing your DB.FORCE ROW LEVEL SECURITY. Without this, the table owner bypasses your RLS policies — a common mistake.GRANT SELECT (id, name) ON users TO analytics;set_config(..., true) (local to transaction) when setting RLS context variables. This resets automatically when the transaction ends — preventing context leakage between requests.pg_audit extension. Log all DDL, role changes, and sensitive queries for compliance (SOC2, HIPAA, GDPR).information_schema.role_table_grants to catch over-permissioned users.Full Security Summary
| Concept | What It Does | Key Command |
|---|---|---|
| User | A role with LOGIN — can connect to the DB | CREATE USER name WITH PASSWORD '...' |
| Role | A named bundle of privileges (no LOGIN) | CREATE ROLE name |
| Privilege | A specific permission (SELECT, INSERT, etc.) | — |
| GRANT | Give a privilege or role to a user/role | GRANT SELECT ON table TO user |
| REVOKE | Remove a privilege or role | REVOKE SELECT ON table FROM user |
| RLS | Auto-filter rows based on who is querying | ALTER TABLE t ENABLE ROW LEVEL SECURITY |
| Policy | The WHERE clause that RLS attaches | CREATE POLICY name ON t USING (expr) |
| set_config | Set a session variable for RLS context | set_config('app.org_id', '42', true) |