Skip to content

Authentication Guide

PostgreSQL roles on the wire, signed JWTs on REST


Overview

heliosdb-lite start serves two network surfaces, and each one authenticates on its own:

SurfaceDefault addressHow clients authenticate
PostgreSQL wire127.0.0.1:5432--auth trust (default), password, md5 or scram-sha-256
REST API (/v1, /api/v1)127.0.0.1:8080Authorization: Bearer <JWT>, an HS256 token signed with HELIOSDB_JWT_SECRET

Both surfaces run SQL as a database role, so GRANT privileges and row-level security apply to the caller. Superusers bypass row-level security. The default bootstrap superusers are postgres and helios.


PostgreSQL Wire

The default --auth trust accepts every connection without a password. Any client can then claim any role, including postgres. Use it for local development only. For anything shared, start the server with SCRAM:

Terminal window
export HELIOSDB_JWT_SECRET="$(openssl rand -hex 32)"
heliosdb-lite start --data-dir ./data --auth scram-sha-256 --password 'choose-a-strong-password'

--password sets the password of the bootstrap users postgres and helios:

Terminal window
psql "postgresql://postgres@127.0.0.1:5432/heliosdb"

Add --tls-cert and --tls-key (PEM files) to encrypt PostgreSQL wire connections. Row-level security protects data only when clients authenticate as non-superuser roles with password, md5 or scram-sha-256.


REST API: JWT Bearer Tokens

Configure the signing secret

The REST API refuses to start unless HELIOSDB_JWT_SECRET is set. The server validates tokens with that secret; it does not issue them. Lite has no sign-up, login or refresh endpoint. A built-in token service is planned. Until then, your application or identity service mints the tokens.

Required claims

ClaimValue
subThe database role the request runs as (privileges and row-level security use it)
tenant_idTenant identifier string
client_idA UUID identifying the client
iat, nbf, expIssued-at, not-before and expiry, as Unix timestamps
jtiUnique token ID
issheliosdb-api
audheliosdb-client
scopesArray of strings (may be empty)

The algorithm is HS256. Tokens with a wrong issuer or audience, an expired exp, or a future nbf are rejected with 401.

Mint a token (Python)

import os, time, uuid
import jwt # pip install PyJWT
now = int(time.time())
token = jwt.encode(
{
"sub": "app_user", # a role that exists in the database
"tenant_id": "default",
"client_id": str(uuid.uuid4()),
"iat": now, "nbf": now, "exp": now + 3600,
"jti": str(uuid.uuid4()),
"iss": "heliosdb-api",
"aud": "heliosdb-client",
"scopes": [],
},
os.environ["HELIOSDB_JWT_SECRET"],
algorithm="HS256",
)
print(token)

Use the token

Terminal window
export TOKEN="$(python3 mint_token.py)"
curl -X POST http://127.0.0.1:8080/v1/branches/main/query \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT current_user"}'

Create the role named in sub first, over the PostgreSQL wire as a superuser, and grant it what it needs:

CREATE ROLE app_user LOGIN PASSWORD 'another-strong-password';
GRANT SELECT, INSERT, UPDATE, DELETE ON users TO app_user;

API keys

The REST middleware also accepts an X-API-Key header, but only for keys that an application registers when it embeds the REST server. The stock heliosdb-lite start registers no API keys, so use JWTs with it.


Multi-Tenant Authentication

Because sub becomes the database role, row-level security policies see the REST caller exactly as they see a PostgreSQL wire session for that role. Issue one role per tenant, or per application user, and write policies against current_user. Do not mint tokens for superusers for tenant traffic, because superusers bypass row-level security.


Security Best Practices

  1. Keep the secret secret: generate HELIOSDB_JWT_SECRET with at least 32 random bytes and store it in your secret manager, not in source control.
  2. Short expiry: mint tokens with a short exp (for example one hour) and mint a new one when it lapses.
  3. TLS in front of REST: the REST port serves plain HTTP. Put a TLS-terminating reverse proxy in front of it for any non-local traffic.
  4. Browsers: cross-origin requests are refused unless you list exact origins in HELIOSDB_CORS_ALLOWED_ORIGINS (comma-separated; * is ignored).
  5. No trust auth in production: use --auth scram-sha-256 with --password on the PostgreSQL wire.

Troubleshooting

ResponseCauseFix
401 {"error":"Unauthorized","message":"Missing or invalid authentication credentials"}No Authorization headerSend Authorization: Bearer <token>
401 {"error":"Unauthorized","message":"Invalid or expired JWT token"}Wrong secret, issuer or audience; expired or not yet validRe-mint the token with the claims above
Server exits at start with a HELIOSDB_JWT_SECRET errorSecret not setExport HELIOSDB_JWT_SECRET before heliosdb-lite start
Query returns permission errorsThe sub role lacks privilegesGRANT the privileges to that role

API Reference