Authentication

How clients prove who they are: trust, md5, SCRAM-SHA-256, JWT, and LDAP/AD — plus identities in config and at runtime.

How clients prove who they are to Dataglot's pgwire port, and how login identities are defined — both in config and at runtime over SQL.

Note. Authorization (what an authenticated user may see — column masks, row filters, per-org policy) is covered in runtime configuration and access control. This page is only about who you are.

Modes

The [auth] mode key selects the method:

ModeBehaviourUse for
trust (default)The asserted username is trusted with no password.Local dev only.
md5Each connection completes a Postgres MD5 password exchange.Legacy clients.
scram-sha-256Each connection completes a Postgres SCRAM-SHA-256 (SASL) challenge–response.Everything else.
jwtThe client presents a signed JWT as its password; its verified groups claim drives directory-group policy. Requires [auth.jwt].IdP / token auth.
ldapThe connection binds to the directory as the user; a group search drives directory-group policy. Requires [auth.ldap].LDAP / Active Directory.
[auth]
mode = "scram-sha-256"

trust preserves the zero-friction local-dev experience: psql "host=127.0.0.1 user=anyone" connects. Never run trust on a reachable network.

scram-sha-256 vs md5. Both authenticate against the same credentials (the sections below apply unchanged to either), so switching is a one-line config change with no re-provisioning. SCRAM-SHA-256 is the stronger choice and should be preferred: it is a salted challenge–response (RFC 5802) — the server sends a random challenge, the client proves knowledge of the password without ever transmitting it, and nothing that crosses the wire is a replayable password-equivalent. md5's salted hash, by contrast, is a bearer secret an eavesdropper can capture and replay. Any modern Postgres client (psql, libpq, JDBC, tokio-postgres, …) negotiates SCRAM automatically when the server offers SASL, so no client change is needed.

Neither mode removes the need for TLS. Both still require a cleartext-equivalent credential at rest (the server has to verify the password without the client's cooperation — inherent to md5 and SCRAM alike; SCRAM stores a salted derivation, not the raw password, but it is still credential-equivalent). And SCRAM's non-replayable handshake still leaves your query results crossing the network in the clear. For production add a [pgwire_tls] block with mode = "require" so the whole session is encrypted. The server logs a warning if you enable either password mode without ingress TLS.

Identities from config

Under md5, each login name is an identity. The password itself is never written to the config file — password_env names an environment variable the server reads once at boot (credentials never live in config or logs):

[auth]
mode = "md5"

[identities.admin]
password_env = "DATAGLOT_ADMIN_PW"

[identities.analyst]
password_env = "DATAGLOT_ANALYST_PW"
org = "acme"
groups = ["analyst"]
export DATAGLOT_ADMIN_PW='choose-a-strong-secret'
export DATAGLOT_ANALYST_PW='another-secret'
dataglot --config dataglot.toml
  • org — the tenant the identity belongs to (see Multi-org below). Omit for the default org.
  • groups — org-group memberships that activate tag-based governance policies.
  • An identity with no password_env cannot log in under md5 (no credential), though its profile still resolves for authorization if it authenticates some other way.

Identities at runtime (no restart)

You don't have to redeploy to add a user. Connected as an existing user, issue SQL DDL — the identity is stored in the meta store and survives restart:

CREATE USER analyst WITH PASSWORD 'a-secret';
ALTER USER analyst WITH PASSWORD 'rotated';
DROP USER analyst;

Runtime identities and config identities coexist; on a name collision the store wins. The full DDL surface (secrets, catalogs, masks, row filters, users, roles) is in runtime configuration.

Multi-org (global-unique usernames)

Dataglot is multi-tenant: identities, catalogs, and policies are scoped to an org. Because md5 auth happens before any per-connection org is known, usernames are unique across all orgs. At login the meta store is scanned for the username, and the match determines the org your session runs in — so a user created while scoped to org acme authenticates and is governed as acme, with no change to the connection string:

PGPASSWORD='a-secret' psql "host=127.0.0.1 user=analyst dbname=pg"
# -> resolves `analyst` to org `acme`; the session sees acme's catalogs + policies

CREATE USER rejects a name already taken in another org (the name must be globally unique). Re-creating the same user in the same org stays idempotent.

Failure semantics

Authentication is fail-closed. An unknown user, a passwordless user, a wrong password, or any store/decrypt error all fail the login identically — there is no distinction a client could use to probe which usernames exist, and the reason is never logged.

Authorization (what you may read)

Authentication proves who you are; authorization decides what you may see. It's a separate layer, off by default (authz.mode = "open"; set "grant" for deny-unless-granted USAGE+SELECT), and it also covers column masks and row filters (which even a superuser can't bypass). See access control for the model and runtime configuration for the GRANT/REVOKE DDL.