CPCODELAB
PostgreSQL

Roles, GRANT & Row-Level Security

13 min read

Roles, GRANT & Row-Level Security

PostgreSQL uses a unified concept of roles for both users (login roles) and groups (nologin roles). This makes it straightforward to assign permissions to a role and then grant that role to multiple users.

Creating Roles

sql
-- Create a login role (a 'user')
CREATE ROLE alice LOGIN PASSWORD 'hunter2';

-- Create a group role (no login)
CREATE ROLE readonly_group NOLOGIN;

-- Grant the group role to a user
GRANT readonly_group TO alice;

-- Create a superuser (rarely needed outside of DBA tasks)
CREATE ROLE dba_user SUPERUSER LOGIN PASSWORD 'strongpass';

Object-Level Privileges

sql
-- Grant SELECT on all current tables in public schema
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_group;

-- Grant DML to an application role
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_role;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_role;

-- Ensure future tables inherit the same grants
ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_role;

-- Revoke the ability to create objects in public schema (security hardening)
REVOKE CREATE ON SCHEMA public FROM PUBLIC;

Row-Level Security (RLS)

Row-Level Security lets you define policies that restrict which rows a given role can see or modify. It is the foundation of multi-tenant data isolation in platforms like Supabase.

sql
-- Enable RLS on a table
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Policy: users can only see their own posts
CREATE POLICY posts_owner_select
  ON posts
  FOR SELECT
  USING (author_id = current_setting('app.current_user_id')::bigint);

-- Policy: users can only update their own posts
CREATE POLICY posts_owner_update
  ON posts
  FOR UPDATE
  USING (author_id = current_setting('app.current_user_id')::bigint);

-- Admins bypass all policies (they are superusers or use BYPASSRLS)
ALTER ROLE admin_role BYPASSRLS;

Table owners bypass RLS by default. If your application connects as the table owner, RLS policies will be silently ignored. Always connect as a limited application role, not the schema owner, when you rely on RLS for data isolation.

Inspecting Privileges

sql
-- See privileges on tables
\dp posts    -- in psql

-- Or via information_schema
SELECT grantee, privilege_type
FROM information_schema.role_table_grants
WHERE table_name = 'posts';
Ready to test yourself?
Practice PostgreSQL— quiz & coding exercises