60 seconds
A multi-user API,
and you write no code
Every row belongs to whoever created it. Nobody can read anyone
else's. There is no schema to write, no migration to run, no middleware to
remember — and nothing to deploy but one file.
The whole thing
# 1. one binary, no runtime, no container
curl -L https://github.com/javimosch/bkn/releases/latest/download/bkn -o bkn
chmod +x bkn
# keeps this demo in ./demo.db instead of your real store at ~/.bkn
export BKN_DATA=./demo.db
export BKN_ADMIN_TOKEN=dev-token
# 2. the only line that matters: who owns a row, and who may touch it
./bkn store create notes/entries \
--owner-field user_id \
--access read=owner --access create=owner \
--access update=owner --access delete=owner
# 3. two people
echo -n 'ada-password' | ./bkn auth user create ada@example.io --password-stdin
echo -n 'bob-password' | ./bkn auth user create bob@example.io --password-stdin
./bkn serve &
That is the setup. Now log in as each of them and watch what happens.
login() { curl -s localhost:7799/v1/auth/login -H 'Content-Type: application/json' \
-d "{\"email\":\"$1\",\"password\":\"$2\"}" | jq -r .tokens.access_token; }
ADA=$(login ada@example.io ada-password)
BOB=$(login bob@example.io bob-password)
# ada writes a note. look closely: there is no user_id in this request
curl -s localhost:7799/v1/store/notes/entries -H "Authorization: Bearer $ADA" \
-H 'Content-Type: application/json' -d '{"text":"ada note"}' | jq -c .record
{"id":"01M395D4R200HTP291HG48XNDT","text":"ada note",
"user_id":"01M395D1FX3RMEG0MHTB7K8QM7"}
The server stamped it. Ada never sent a user_id and could not have
got it wrong.
# each of them reads the same URL
N=localhost:7799/v1/store/notes/entries
curl -s $N -H "Authorization: Bearer $ADA" | jq -c '[.records[].text]'
curl -s $N -H "Authorization: Bearer $BOB" | jq -c '[.records[].text]'
curl -s -o /dev/null -w '%{http_code}\n' $N # nobody
ada: ["ada note"]
bob: ["bob note"]
nobody: 401
Now try to cheat
Ada knows Bob's user id. She writes a row and claims it is his.
curl -s localhost:7799/v1/store/notes/entries -H "Authorization: Bearer $ADA" \
-H 'Content-Type: application/json' \
-d '{"text":"forged","user_id":"01M395D1K538BXDMB5NW98ZPAE"}' \
| jq -c '.record | {text, user_id}' # that is bob's id
{"text":"forged","user_id":"01M395D1FX3RMEG0MHTB7K8QM7"} ← ada's id, not bob's
bob still sees: ["bob note"]
It did not return an error. It returned the truth.
The owner is not a field the caller gets to set — it is stamped from the token,
so a request that lies about it is simply corrected. There is no code path where
forgetting a check lets it through, because there is no check: the owner is part
of the query, not a condition tested beside it.
What you did not write
- A users table, password hashing, and token issuing and verification.
- An auth middleware, and the discipline of putting it on every route.
- The
WHERE user_id = ? you must never forget. Every
leak of this shape is one query somebody wrote without it.
- A migration, and the second one when the shape changed.
- A container, a database server, a connection string, a deploy.
The interesting one is the third. Owner scoping being part of the read
rather than a guard in front of it is the difference between "we remembered
everywhere" and "it cannot be forgotten".
Where this goes next
Swap owner for org and the same two lines give you
tenant isolation instead of per-user. Swap it for public and you have a
read-only API for a front end. When you need real behaviour rather than storage —
validating a form, verifying a webhook signature, exporting a CSV — that is a
JavaScript file you install with bkn script create, not a service you
stand up.