Lesson 24 · PostgreSQL in Go
RLS & multi-tenancy
How one database safely serves every tenant — from JWT to row.
Your win: explain the central pattern of this codebase — Row-Level
Security — and trace resource_path from the JWT all the way to a filtered row.
This is where all four courses meet.
The problem: many tenants, shared tables
Every school/org shares the same emails, conversation, … tables.
How do you guarantee tenant A never sees tenant B's rows — even if a query forgets
a tenant filter? Postgres Row-Level Security (RLS).1
The pattern (in every tenant table)
migrations/auth/1001 (funcs) · migrations/notificationmgmt/1004 (emails)-- column, auto-stamped on insert:
resource_path text NOT NULL DEFAULT autofillresourcepath();
-- policy: a row is visible only if it belongs to the session's tenant
CREATE POLICY rls_emails ON emails
USING (permission_check(resource_path, 'emails')) -- = (resource_path = current_setting('permission.resource_path'))
WITH CHECK (permission_check(resource_path, 'emails'));
ALTER TABLE emails ENABLE ROW LEVEL security;
ALTER TABLE emails FORCE ROW LEVEL security; -- applies even to the table owner
WHERE resource_path = …. Even a
buggy query returns only the current tenant's rows. That's why RLS is the
security backbone, not just a convenience.
The trace: JWT → row (all four courses meet)
setPostgres (pool_gcp.go:161) runs on every
connection checkout and set_configs the tenant from the JWT claims; it even
verifies the echoed value and rejects the connection on mismatch. Note it's session-level
(false), re-set each acquire — so a tx on that connection
inherits the tenant. Hasura (the read path) doesn't use Postgres RLS; it
re-implements the same filter in metadata (resource_path _eq
X-Hasura-Resource-Path). Two enforcement layers, one tenant boundary.
PostgreSQL — Row Security Policies + CREATE POLICY
The authoritative guide to RLS, USING/WITH CHECK,
permissive vs restrictive policies, and FORCE ROW LEVEL SECURITY.
→ postgresql.org/docs — Row Security Policies
→ CREATE POLICY
Check yourself (from memory)
Q1. RLS enforces multi-tenancy by…
resource_path against the
session's tenant on every row — shared tables, isolated rows.
Q2. The tenant (resource_path) is set on the connection by…
setPostgres runs set_config on
each checkout, from the JWT claims on the context.
Q3. RLS is valuable because it filters even when…
resource_path) on the request context (gRPC L10/L13). (2) When a query
needs a connection, the pool's BeforeAcquire hook (setPostgres)
runs set_config('permission.resource_path', <from claims>, false) on it
(Lesson 21). (3) Every tenant table has RLS enabled with a policy
permission_check(resource_path) = resource_path =
current_setting('permission.resource_path'), plus FORCE ROW LEVEL
SECURITY. So Postgres filters every row to the current tenant — even a query that
forgot a tenant WHERE. resource_path is indexed (Part 3) and
selective (Part 4). Hasura re-implements the same filter for its read path.1. PostgreSQL — Row Security Policies; repo: repo-postgres-map.md.