Chapter 16 · Security, Reliability, and Governance

Authentication, Authorization, Roles, and Least Privilege

Database security begins by proving who is connecting and then restricting what that identity can do. A maintainable design assigns privileges to job roles, keeps application accounts non-administrative, and verifies access continuously instead of relying on intent.

Intermediate145–180 minutesIdentity architecture + privilege laboratoryLast reviewed: August 2026

Learning outcomes

Design access from identities to verified privileges

01

Distinguish authentication, authorization, ownership, and accountability.

02

Model human, service, migration, and administrative identities separately.

03

Assign privileges through PostgreSQL group roles and revoke unsafe defaults.

04

Use schemas, views, and row-level security as additional authorization boundaries.

05

Audit effective privileges and test denied operations as first-class requirements.

Four questions for every database action

ID

Authentication

Who or what established this database session, and how was the credential verified?

ACL

Authorization

Which operations may the effective role perform on this database object?

OWN

Ownership

Which role controls the object definition and may delegate privileges?

LOG

Accountability

Can an investigator connect the action to a person, service, request, and outcome?

Authentication can succeed while authorization correctly denies the statement. Conversely, a broadly privileged shared account may authenticate correctly but provide weak accountability.

Use job roles, not privilege-by-person

Login identity
Membership in job role
Schema privilege
Object privilege
Optional row policy
Allowed statement

Separating LOGIN identities from reusable privilege roles makes reviews, onboarding, and revocation predictable.

Identity typeTypical capabilityProhibited by default
Application runtimeRead/write only through required tables or viewsDDL, role management, server files, unrestricted maintenance
Read-only analystSELECT on curated schemas or viewsWrites, raw secrets, administrative catalogs
Migration runnerVersioned DDL during deployment windowPermanent application traffic and superuser operation
Backup operatorBackup-specific functions and storage accessBusiness-data modification
Human administratorTime-bounded elevated operation with auditDaily application use through the admin account

PostgreSQL role hierarchy

PostgreSQL uses roles for both users and groups. Prefer non-login roles as privilege bundles and separate login roles for services or people.

postgresql · role and privilege design
-- Reusable privilege bundles do not log in.CREATE ROLE app_reader NOLOGIN;CREATE ROLE app_writer NOLOGIN;CREATE ROLE reporting_reader NOLOGIN;-- Service identities authenticate but do not own application objects.CREATE ROLE checkout_service LOGIN PASSWORD :'managed_secret';CREATE ROLE reporting_service LOGIN PASSWORD :'managed_secret';GRANT app_reader, app_writer TO checkout_service;GRANT reporting_reader TO reporting_service;-- Remove broad implicit access before granting the intended surface.REVOKE ALL ON DATABASE academy FROM PUBLIC;GRANT CONNECT ON DATABASE academy TO checkout_service, reporting_service;REVOKE ALL ON SCHEMA public FROM PUBLIC;GRANT USAGE ON SCHEMA commerce TO app_reader, app_writer;GRANT USAGE ON SCHEMA reporting TO reporting_reader;GRANT SELECT ON commerce.customer, commerce.sales_order TO app_reader;GRANT INSERT, UPDATE ON commerce.sales_order TO app_writer;GRANT SELECT ON reporting.customer_order_summary TO reporting_reader;-- Sequences need separate privileges when generated identifiers use them.GRANT USAGE, SELECT ON SEQUENCE commerce.sales_order_order_id_seq TO app_writer;

Ownership and deployment roles

The runtime account should normally not own tables. An owner can alter or drop its objects and may grant privileges. A safer deployment separates the owner, migration runner, and runtime service.

postgresql · controlled ownership
CREATE ROLE commerce_owner NOLOGIN;CREATE ROLE migration_runner LOGIN;GRANT commerce_owner TO migration_runner;ALTER SCHEMA commerce OWNER TO commerce_owner;ALTER TABLE commerce.customer OWNER TO commerce_owner;ALTER TABLE commerce.sales_order OWNER TO commerce_owner;-- During a migration session:SET ROLE commerce_owner;ALTER TABLE commerce.sales_order    ADD COLUMN IF NOT EXISTS source_channel text;RESET ROLE;

Column and row boundaries

Object privileges answer whether a role may use a relation. Column privileges and row-level security can narrow that surface, but complexity increases quickly. Curated views are often easier to review for column exposure.

postgresql · row-level security example
ALTER TABLE commerce.sales_order ENABLE ROW LEVEL SECURITY;CREATE POLICY order_region_policyON commerce.sales_orderFOR SELECTTO regional_supportUSING (    region = current_setting('app.region', true));-- With RLS enabled and no applicable policy, PostgreSQL applies default deny.-- Owners and roles with BYPASSRLS require separate governance.
sqlite · curated support view
CREATE VIEW customer_support_view ASSELECT    customer_id,    substr(email, 1, 2) || '***@' || substr(email, instr(email, '@') + 1) AS masked_email,    region,    CASE WHEN deleted_at IS NULL THEN 'active' ELSE 'deleted' END AS lifecycle_stateFROM customer;SELECT *FROM customer_support_viewORDER BY customer_id;

Verify effective access

Reviews must test both expected success and expected denial. A role matrix that says “read only” is not evidence until the database confirms it.

postgresql · privilege inspection
SELECT    current_user,    session_user,    has_database_privilege(current_user, 'academy', 'CONNECT') AS can_connect,    has_schema_privilege(current_user, 'commerce', 'USAGE') AS can_use_schema,    has_table_privilege(current_user, 'commerce.sales_order', 'SELECT') AS can_read_orders,    has_table_privilege(current_user, 'commerce.sales_order', 'DELETE') AS can_delete_orders;SELECT grantee, table_schema, table_name, privilege_typeFROM information_schema.role_table_grantsWHERE table_schema IN ('commerce', 'reporting')ORDER BY grantee, table_schema, table_name, privilege_type;

Access review

  1. Why should the application runtime role not own its tables?
  2. What is the difference between a login role and a privilege-bundle role?
  3. Why must a security test include denied statements?
  4. When might a view be easier to govern than column-level grants?
Review the answers

Ownership permits definition changes and delegation, which runtime code rarely needs. A login role authenticates; a non-login group role collects privileges. Denial tests detect accidental grants and unsafe defaults. A view presents one explicit, reviewable column contract and can centralize masking or filtering.

Operational rules

  • Use unique service identities and managed secret rotation; never embed administrator credentials in application code.
  • Grant privileges to roles, grant role membership to identities, and remove membership promptly.
  • Revoke PUBLIC access deliberately rather than assuming defaults are harmless.
  • Keep administrative elevation time-bounded and auditable.
  • Review owners, memberships, object grants, default privileges, row policies, and dormant accounts.

References

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.