Roles, privileges, Row-Level Security policies, and multi-tenant data isolation — the production access model.
P-6 — Authentication, Row-Level Security, and Access Control
Most tutorials treat PostgreSQL authentication as a one-liner: create a superuser, give it a password, connect. That works in development. In production it is a security incident waiting to happen.
This module covers the PostgreSQL access model from first principles — roles, privileges, and the Row-Level Security system that lets the database enforce data boundaries between tenants without any application-layer code. By the end you will have the mental model to design a proper production access architecture, and a working multi-tenant RLS setup you can adapt for real projects.
Why the Default Setup Is Dangerous
When you install PostgreSQL locally, the postgres superuser is created automatically. When you deploy to Heroku, Railway, or Supabase, you get a user with broad privileges. Most tutorials say "connect as this user from your app" and move on.
Here is what that means in practice:
- Your application can
DROP TABLEits own data - A SQL injection vulnerability has
DELETEaccess to every table - One compromised connection can exfiltrate the entire database
- There is no way to give a read-only analytics tool limited access
The fix is a proper role hierarchy with the principle of least privilege. PostgreSQL has a first-class system for this.
Roles Are Everything
PostgreSQL does not have "users" and "groups" as separate concepts. It has roles. A role can:
- Log in (it becomes a "user")
- Own objects (tables, sequences, schemas)
- Have other roles granted to it (role inheritance)
- Grant its privileges to other roles
Everything is a role. CREATE USER is just syntax sugar for CREATE ROLE ... WITH LOGIN.
Creating Roles
When app_user logs in, it has all privileges that readonly_role has. This is role inheritance.
Role Attributes
Key attributes when creating a role:
| Attribute | Meaning |
|---|---|
LOGIN | Can authenticate and open a session |
SUPERUSER | Bypasses all access checks — avoid in production |
CREATEDB | Can create new databases |
CREATEROLE | Can create other roles |
REPLICATION | Used for streaming replication connections |
BYPASSRLS | Bypasses Row-Level Security policies |
PASSWORD 'x' | Sets the login password |
Granting and Revoking Privileges
PostgreSQL privileges are granted on specific objects: tables, sequences, schemas, databases, functions.
The GRANT Syntax
ALTER DEFAULT PRIVILEGES is the one most people miss. Without it, every new table you create needs a separate GRANT. With it, new tables automatically get the right privileges.
Revoking Privileges
Checking What a Role Can Do
A Production Role Architecture
Here is a pattern that works well for production applications:
Your migration tool connects as myapp_owner. Your application connects as myapp_app. Your BI tool connects as myapp_reader. A SQL injection in the app cannot drop tables or read tables the app role has no access to.
Row-Level Security (RLS)
Role-based access control gets you table-level isolation. Row-Level Security gets you row-level isolation. This is the mechanism behind Supabase's per-user data access, multi-tenant SaaS isolation, and any system where different users should see different subsets of the same table.
How RLS Works
When RLS is enabled on a table, every query against that table automatically gets an invisible WHERE clause appended — the clause defined by the policy. No application code required.
Sign in to keep reading
The rest of this module is free — sign in with Google to unlock it and track your progress.
Sign in & RegisterDiscussion
0Join the discussion