11.5. Permissions
Table of Contents
Understanding Permissions in PostgreSQL
PostgreSQL has a powerful permission system that controls who can do what with which database objects. As a backend developer you must understand this so your application does not accidentally expose or destroy data.
This chapter focuses only on PostgreSQL permissions. General authentication and authorization concepts are covered in other chapters.
Roles, Users, and Groups
PostgreSQL uses roles to represent both individual users and groups.
- A role can log in like a user account.
- A role can also own objects like tables or schemas.
- A role can contain other roles, which works like a group.
You create and manage permissions for roles, not directly for connections.
Creating Roles
You can create a role with SQL:
CREATE ROLE app_user;
CREATE ROLE app_admin;
By default, these roles cannot log in. To make a role that can log in, you give it the LOGIN attribute:
CREATE ROLE backend_user WITH LOGIN PASSWORD 'very_secret';
You can also use CREATE USER which is just a shortcut for CREATE ROLE ... LOGIN:
CREATE USER api_user WITH PASSWORD 'another_secret';
Functionally, USER and ROLE are the same. USER implies LOGIN.
Role Attributes
Roles have special attributes that control high-level power:
| Attribute | Description | Example use |
|---|---|---|
| LOGIN | Can authenticate and connect | Application accounts, human users |
| SUPERUSER | Bypass all permission checks | Database admin only |
| CREATEDB | Can create databases | Dev ops, database admin |
| CREATEROLE | Can create and manage roles | Database admin |
| INHERIT | Inherit privileges from roles it is a member of | Usually left on (default) |
| REPLICATION | Can initiate replication | Replication users |
| BYPASSRLS | Bypass row level security | Trusted admin roles |
Example:
CREATE ROLE app_admin
WITH LOGIN
CREATEDB
CREATEROLE
PASSWORD 'admin_password';
Rule: Never give SUPERUSER to an application role. Superusers can do anything, including dropping databases and reading all data.
GRANT and REVOKE Basics
Most permission management in PostgreSQL uses two commands:
GRANTto give privilegesREVOKEto remove privileges
You use them on specific objects such as databases, schemas, tables, sequences, and functions.
Basic pattern:
GRANT privilege_list
ON object_type object_name
TO role_name;
REVOKE privilege_list
ON object_type object_name
FROM role_name;Common Privileges by Object Type
| Object type | Common privileges |
|---|---|
| DATABASE | CONNECT, CREATE, TEMP |
| SCHEMA | USAGE, CREATE |
| TABLE | SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER |
| SEQUENCE | USAGE, SELECT, UPDATE |
| FUNCTION | EXECUTE |
| TYPE | USAGE |
Examples:
Give SELECT and INSERT on a table to a role:
GRANT SELECT, INSERT
ON TABLE public.users
TO app_user;
Remove INSERT:
REVOKE INSERT
ON TABLE public.users
FROM app_user;Give a role permission to connect to a database:
GRANT CONNECT
ON DATABASE myapp
TO app_user;Remove that permission:
REVOKE CONNECT
ON DATABASE myapp
FROM app_user;Public Role
PostgreSQL has a special built-in role called PUBLIC.
- Every role is automatically a member of
PUBLIC. - Any privilege granted to
PUBLICis granted to everyone.
Example:
GRANT CONNECT
ON DATABASE myapp
TO PUBLIC;
This means all users can connect to myapp.
Rule: Be careful with PUBLIC. If you grant SELECT on a table to PUBLIC, every user can read that table.
Database Level Permissions
Database-level permissions control whether a role can access a particular database.
The main database-level privileges are:
CONNECTconnect to the databaseCREATEcreate schemas in the databaseTEMPcreate temporary tables
Example: restrict who can connect to a database.
-- Remove connect rights from everyone
REVOKE CONNECT ON DATABASE myapp FROM PUBLIC;
-- Allow only specific roles
GRANT CONNECT ON DATABASE myapp TO app_user;
GRANT CONNECT ON DATABASE myapp TO app_admin;
Try it from psql as a role with no permission:
psql -U random_user -d myapp
You will see a permission denied error if random_user does not have CONNECT.
Schema Level Permissions
Schemas are namespaces that group tables, functions, and other objects.
Schema privileges:
USAGEuse the schema name and access objects inside it, if you also have privileges on those objects.CREATEcreate new objects inside the schema.
Example: create a schema and control access.
CREATE SCHEMA app AUTHORIZATION app_admin;
Give USAGE to normal application users so they can see and use tables in that schema:
GRANT USAGE
ON SCHEMA app
TO app_user;Allow only admins to create new tables and functions there:
REVOKE CREATE
ON SCHEMA app
FROM PUBLIC;
GRANT CREATE
ON SCHEMA app
TO app_admin;
Without USAGE, a role cannot even see that a schema exists.
Table and Column Permissions
Most of the time, you manage permissions on tables.
Table privileges:
SELECTread rowsINSERTadd rowsUPDATEchange existing rowsDELETEdelete rowsTRUNCATEremove all rowsREFERENCEScreate foreign keys referencing this tableTRIGGERcreate triggers on the table
Basic Table Permissions
-- Allow reading from the users table
GRANT SELECT
ON TABLE app.users
TO app_user;
-- Allow full access to admins
GRANT SELECT, INSERT, UPDATE, DELETE
ON TABLE app.users
TO app_admin;
If a role does not have SELECT, tries to read:
SELECT * FROM app.users;It will get:
ERROR: permission denied for table usersColumn-Level Permissions
You can restrict permissions to specific columns:
-- app_analytics can see only id and created_at
GRANT SELECT (id, created_at)
ON app.users
TO app_analytics;Now:
SET ROLE app_analytics;
SELECT id, created_at FROM app.users; -- works
SELECT id, email FROM app.users; -- permission denied
SELECT * FROM app.users; -- permission denied
Column-level permissions apply only to SELECT, INSERT, and UPDATE.
Sequence and Function Permissions
Sequence Permissions
Sequences generate auto-increment values, often for primary keys.
Privileges:
USAGEusenextval,currval, etc.SELECTread the current valueUPDATEchange the current value
If you have a table like:
CREATE TABLE app.users (
id bigserial PRIMARY KEY,
email text NOT NULL
);
PostgreSQL creates a sequence, for example app.users_id_seq.
To let app_user insert rows, you must allow access to the sequence as well:
GRANT USAGE, SELECT
ON SEQUENCE app.users_id_seq
TO app_user;If you forget this, inserting may fail with a permission error on the sequence.
Function Permissions
Functions can encapulate logic and access tables. They have an EXECUTE privilege.
Example function:
CREATE FUNCTION app.get_user_count()
RETURNS integer
LANGUAGE sql
AS $$
SELECT COUNT(*) FROM app.users;
$$;By default, only the function owner (often the role that created it) can execute it.
Allow app roles to use it:
GRANT EXECUTE
ON FUNCTION app.get_user_count()
TO app_user;
Now app_user can run:
SELECT app.get_user_count();This is useful to give restricted or computed data access without exposing full table access.
Managing Permissions with Role Hierarchies
Instead of assigning permissions to each user separately, you usually:
- Define group roles that represent types of access.
- Grant privileges to these group roles.
- Add users or application roles as members of these group roles.
This keeps your permission setup clean and easier to maintain.
Example Hierarchy
-- Group roles (no login)
CREATE ROLE app_readonly;
CREATE ROLE app_writer;
CREATE ROLE app_admin;
-- Grant permissions to group roles
-- Read-only access to public data
GRANT USAGE ON SCHEMA app TO app_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO app_readonly;
-- Read-write access to main tables
GRANT app_readonly TO app_writer;
GRANT INSERT, UPDATE, DELETE
ON ALL TABLES IN SCHEMA app
TO app_writer;
-- Full access for admins
GRANT app_writer TO app_admin;
ALTER DEFAULT PRIVILEGES IN SCHEMA app
GRANT ALL ON TABLES TO app_admin;Now create actual users and attach them to these group roles:
CREATE USER api_readonly WITH PASSWORD 'x';
CREATE USER api_writer WITH PASSWORD 'y';
CREATE USER db_admin WITH PASSWORD 'z';
GRANT app_readonly TO api_readonly;
GRANT app_writer TO api_writer;
GRANT app_admin TO db_admin;The individual users inherit the privileges from group roles.
Viewing Role Membership
To see which roles are members of which:
SELECT
rolname AS role_name,
member.rolname AS member_name
FROM pg_roles role
JOIN pg_auth_members m
ON role.oid = m.roleid
JOIN pg_roles member
ON member.oid = m.member;
In psql, you can also run:
\duto see a list of roles and some attributes.
Ownership and Default Privileges
Object Ownership
Every object in PostgreSQL has an owner:
- The owner automatically has all privileges on that object.
- Only the owner or a superuser can change its privileges with
GRANTandREVOKE. - The owner can transfer ownership with
ALTER ... OWNER TO ....
Example:
-- Change owner of a table
ALTER TABLE app.users OWNER TO app_admin;
-- Change owner of a schema
ALTER SCHEMA app OWNER TO app_admin;For applications, it is common to:
- Use a dedicated owner role for schemas and tables.
- Connect from another role that has only the required access, not ownership.
Default Privileges
When you create new tables, sequences, or functions, they inherit default privileges.
By default:
- Only the owner gets privileges.
PUBLIChas no privileges on new tables in non-public schemas.
You can change this behavior with ALTER DEFAULT PRIVILEGES.
Example: ensure every new table in schema app is readable by app_readonly and writable by app_writer.
Execute this as the owner (for example app_admin):
-- For tables
ALTER DEFAULT PRIVILEGES IN SCHEMA app
GRANT SELECT ON TABLES TO app_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA app
GRANT INSERT, UPDATE, DELETE ON TABLES TO app_writer;
-- For sequences (for serial / bigserial)
ALTER DEFAULT PRIVILEGES IN SCHEMA app
GRANT USAGE, SELECT ON SEQUENCES TO app_writer;Now, when you create a new table:
CREATE TABLE app.orders (
id bigserial PRIMARY KEY,
user_id bigint NOT NULL,
total_cents integer NOT NULL
);
app_readonly and app_writer will already have the correct permissions.
Without this, every time you add a new table you would need to run GRANT manually.
Practical Patterns for Backend Apps
Here are a few patterns you can apply in real projects.
Pattern 1: Strict separation of owner and app role
- Create an owner role that is not used by the application:
CREATE ROLE app_owner NOLOGIN;
CREATE ROLE app_readonly NOLOGIN;
CREATE ROLE app_writer NOLOGIN;- Create login-only application roles that inherit group permissions:
CREATE USER app_backend WITH PASSWORD 'secret';
GRANT app_writer TO app_backend;- Make
app_ownerown all objects:
CREATE SCHEMA app AUTHORIZATION app_owner;- As
app_owner, set default privileges:
ALTER DEFAULT PRIVILEGES IN SCHEMA app
GRANT SELECT ON TABLES TO app_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA app
GRANT INSERT, UPDATE, DELETE ON TABLES TO app_writer;- In your application, use
app_backendas the connection user.
Now, even if your application code is compromised, the attacker cannot change table definitions or drop the schema, since the app user is not the owner.
Pattern 2: Read-only analytics role
You often need a role for reporting tools or manual analyses.
CREATE ROLE analytics NOLOGIN;
GRANT USAGE ON SCHEMA app TO analytics;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO analytics;
ALTER DEFAULT PRIVILEGES IN SCHEMA app
GRANT SELECT ON TABLES TO analytics;
CREATE USER analytics_user WITH PASSWORD 'analytics_pw';
GRANT analytics TO analytics_user;
Use analytics_user in BI tools. It cannot modify data, only read it.
Common Permission Problems and How to Fix Them
Problem: Application cannot insert into table
Error:
ERROR: permission denied for table usersCheck:
\dp app.users -- in psql, shows privileges on the tableFix:
GRANT INSERT, SELECT
ON TABLE app.users
TO app_writer; -- or your app roleAlso check the sequence:
\ds app.users_id_seq
GRANT USAGE, SELECT
ON SEQUENCE app.users_id_seq
TO app_writer;Problem: New tables not accessible to app
You recently created a new table, but the application gets permission errors only on that table.
Cause: ALTER DEFAULT PRIVILEGES was not set, so the new table did not receive the same grants.
Fix:
- Grant permissions on existing table:
GRANT SELECT, INSERT, UPDATE, DELETE
ON TABLE app.new_table
TO app_writer;- Set default privileges for future tables (if not already):
ALTER DEFAULT PRIVILEGES IN SCHEMA app
GRANT SELECT, INSERT, UPDATE, DELETE
ON TABLES
TO app_writer;Problem: Role can see schema, but cannot access table
Error:
ERROR: permission denied for schema appor
ERROR: permission denied for table usersYou need:
USAGEon the schema- proper privileges on the table
Fix:
GRANT USAGE ON SCHEMA app TO app_user;
GRANT SELECT ON TABLE app.users TO app_user;Summary
- PostgreSQL uses roles for both users and groups.
- Use
GRANTandREVOKEto manage permissions on databases, schemas, tables, sequences, and functions. - Control broad access with database and schema privileges, then fine-tune with table and column privileges.
- Use role hierarchies: create group roles for read-only, read-write, and admin access, then assign users to them.
- Use object ownership and
ALTER DEFAULT PRIVILEGESso new objects automatically get correct permissions. - Avoid
SUPERUSERfor application roles, and be very careful with grants toPUBLIC.
With these tools, you can design database permissions that keep your application secure and maintainable.
Views: 17
KAHIBARO