Get started

Keys and security

Berth has three kinds of keys and one kind of user token. Which one you send decides what a request may do. The rule that matters most: a secret key never ships inside an app.

Key types

PrefixNameWhere it may liveWhat it can do
bak_account keyyour laptop, CIcreate and manage every app the account owns
bsk_secret app keyyour server, functionsfull access to one app: schema, rows, SQL, users, storage, functions, webhooks, export
bpk_publishable app keyphone or browser buildsend-user auth endpoints, and only what table and bucket policies allow
JWTend-user access tokenthe signed-in deviceacts as one signed-in user of one app, subject to policies

Never ship a secret key in an app. Anything inside an iOS, Android, or web bundle can be pulled out by anyone who installs it. A leaked bsk_ key reads and deletes every row, user, and file in that app. Ship the publishable key and set policies instead. If a secret key leaks, create a new one and revoke the old one.

Sending a key

Send any credential as Authorization: Bearer <credential>. A publishable key may instead go in an apikey header, which leaves Authorization free for a user JWT (the supabase-js style). A user JWT alone also works, because it names its app.

curl -sS https://api.atberth.com/v1/me \
  -H "Authorization: Bearer $PUBLISHABLE_KEY"

GET /v1/me tells you what a credential is: role is one of account, secret, publishable, or user.

Managing app keys

An app starts with one secret and one publishable key. Make more so each server, build, or teammate has its own, and revoke one without touching the others. The full key is shown once; lists show only the prefix.

berth keys list --app "$APP"
KEY_ID=$(curl -sS -X POST https://api.atberth.com/v1/apps/$APP/keys \
  -H "Authorization: Bearer $SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"ios build 2","type":"publishable"}' | jq -er .key.id)
echo "$KEY_ID"

Revoke it:

curl -sS -X DELETE https://api.atberth.com/v1/apps/$APP/keys/$KEY_ID \
  -H "Authorization: Bearer $SECRET_KEY"

With the CLI: berth keys create --app "$APP" --type secret --name worker and berth keys revoke --app "$APP" "$KEY_ID".

Account keys

Account keys create, export, restore, and delete apps. berth login makes one per machine. Make a separate one for CI:

curl -sS https://api.atberth.com/v1/account/keys \
  -H "Authorization: Bearer $BERTH_ACCOUNT_KEY"
berth account keys list

Publishable keys and policies

A publishable key on its own can sign users up and in, and nothing else, until you open a table or bucket with a policy. New tables default to secret for reading and writing, so a fresh table is closed to phones:

berth tables create --app "$APP" notes title:text:notnull
curl -sS https://api.atberth.com/v1/apps/$APP/tables/notes/rows \
  -H "Authorization: Bearer $PUBLISHABLE_KEY"

That answers 403 with policy_violation. Open it for signed-in users, each seeing only their own rows:

berth tables policy --app "$APP" notes --read owner --write owner

Policy levels are covered in Row policies.

Tenant isolation

Every app is its own Postgres database, owned by its own Postgres role that cannot log in and cannot see other databases. Requests for an app run as that app's role, so a bug or a hostile SQL statement in one app has no path to another app's tables. On top of that:

  • Keys and user tokens are bound to one app. A key for app A gets 401 or 403 on app B.
  • Signed-in users are limited by Postgres row level security, not only by checks in the API.
  • Functions run in a Deno sandbox with only their own app's env vars and no access to private networks.
  • Env var values are never returned by the API once set.

Browser origins

Each app has cors_origins, default ["*"]. Set it to your site so a publishable key copied into another site stops working from browsers there. Requests from other origins with a publishable key or user token get 403 origin_not_allowed.

berth apps config "$APP" --cors https://notes.example.com
curl -sS -X PATCH https://api.atberth.com/v1/apps/$APP \
  -H "Authorization: Bearer $SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"cors_origins":["*"]}'