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
Defense in depth Because Postgres itself filters every query by the policy, tenant isolation doesn't depend on every developer remembering a 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)

1. gRPC auth interceptor verifies JWT → claims (incl. resource_path) on ctx [gRPC L10/L13] 2. query needs a connection → pool BeforeAcquire hook `setPostgres` [Postgres L21] runs: SELECT set_config('permission.resource_path', <from claims>, false) 3. RLS policy on each row: resource_path = current_setting('permission.resource_path') → only this tenant's rows are visible [Postgres L24] (resource_path is indexed [Part 3] and selective [Part 4])
Anchor — the whole picture 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.
Read this next

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…

A policy checks 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…

Postgres applies the policy regardless of the query — defense in depth against a forgotten filter.
Trace how one row stays tenant-isolated, from JWT to Postgres.
recall, then click to reveal
(1) The gRPC auth interceptor verifies the JWT and puts the claims (incl. 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.
🎓 Course complete — all 24 lessons From the relational model → storage & MVCC → indexes → query performance → transactions & concurrency → Postgres in Go. Everything anchored to your code, and in this final lesson all four of your courses meet: a JWT (gRPC) sets the tenant on a pooled connection (Postgres) that filters rows via RLS — and a committed write flows out through the WAL to Kafka. The last mile now is retrieval under pressure, not more reading — ask me for a mixed mock interview across all four courses.
Ready for the mock interview, or want to go deeper on anything (RLS performance, connection-per-tenant vs shared, Hasura permissions)? Ask me.

1. PostgreSQL — Row Security Policies; repo: repo-postgres-map.md.