JWT Claims Reference

HS256 Claims

Use this mode when CloudSync validates JWTs with jwtSecret.

ClaimRequired?Notes
subDependsCommonly used by application-specific RLS policies
emailNoOptional app-specific claim
roleYesRequired for PostgreSQL JWT-authenticated requests because CloudSync uses it for SET LOCAL ROLE
issNoOptional in HS256 mode
audDependsRequired only when jwtExpectedAudiences is configured
iatNoOptional issued-at timestamp
expYesRequired and validated by CloudSync

JWKS Claims

Use this mode when CloudSync validates JWTs with jwtAllowedIssuers and optional jwksUri.

ClaimRequired?Notes
subDependsCommonly used by application-specific RLS policies
emailNoOptional app-specific claim
roleYesRequired for PostgreSQL JWT-authenticated requests because CloudSync uses it for SET LOCAL ROLE
issYesRequired for JWKS or issuer-based validation
audDependsRequired only when jwtExpectedAudiences is configured
iatNoOptional issued-at timestamp
expYesRequired and validated by CloudSync
Header kidYesRequired in the JWT header so CloudSync can select the verification key from the JWKS

PostgreSQL Role Requirement

For PostgreSQL JWT authentication, the role claim must name a real database role that CloudSync can switch into with SET LOCAL ROLE.

That role should:

  • already exist in PostgreSQL
  • have the schema, table, and sequence privileges your sync operations need
  • be grantable by the connection-string user

If the JWT contains a role that does not exist, or the connection user cannot switch into it, PostgreSQL sync operations will fail even if the JWT itself is otherwise valid.

Creating the Role

CREATE ROLE rls_role NOLOGIN;
GRANT rls_role TO postgres;

Required Grants

cloudsync_payload_apply running as a non-superuser touches several internal CloudSync objects during apply — not just your user table. If any grant is missing on an internal object, the per-PK savepoint silently rolls back the write and the caller sees a non-zero column-change count with no rows landing.

Recommended setup:

GRANT USAGE ON SCHEMA public TO rls_role;
GRANT USAGE ON SCHEMA auth   TO rls_role;

ALTER DEFAULT PRIVILEGES IN SCHEMA public
    GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER
    ON TABLES TO rls_role;

ALTER DEFAULT PRIVILEGES IN SCHEMA public
    GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO rls_role;

ALTER DEFAULT PRIVILEGES IN SCHEMA public
    GRANT EXECUTE ON FUNCTIONS TO rls_role;

If the extension is already installed, backfill existing objects with one-time grants and keep the default-privileges block for future objects.

How CloudSync Passes JWT Claims to PostgreSQL

CloudSync validates the JWT and passes all claims to PostgreSQL via request.jwt.claims. It also executes SET LOCAL ROLE from the JWT role claim.

That means RLS policies can read claims with expressions such as:

current_setting('request.jwt.claims')::jsonb->>'sub'
current_setting('request.jwt.claims')::jsonb->>'role'

Optional Helper Functions

If you want shorter policy expressions, create convenience wrappers:

CREATE SCHEMA IF NOT EXISTS auth;

CREATE OR REPLACE FUNCTION auth.session()
RETURNS jsonb AS $$
  SELECT current_setting('request.jwt.claims', true)::jsonb;
$$ LANGUAGE SQL STABLE;

CREATE OR REPLACE FUNCTION auth.user_id()
RETURNS text AS $$
  SELECT auth.session()->>'sub';
$$ LANGUAGE SQL STABLE;

CREATE OR REPLACE FUNCTION auth.role()
RETURNS text AS $$
  SELECT auth.session()->>'role';
$$ LANGUAGE SQL STABLE;

For RLS behavior and troubleshooting, see the RLS reference.