Previous: minimal server · All tutorials · Next: gateway scaling
You will put a shared auth service in front of the existing game repository,
switch storage to TLS, and give Alice and Bob real identities and roles. One
scribe-ctl add auth does the provisioning; you then add the people and
grants. Run the commands from the scribe-demo directory of stage 1.
1. Finish plaintext work before the switch
The cluster should be stopped after stage 1. If you have continued working,
first finish or resolve pending submits while it still runs in plaintext,
inspect scribe status --scan and scribe lock list in both working copies,
and release held locks. Preserve any unsubmitted or untracked files separately.
Then stop it with scribe-ctl --config scribe-cluster.toml down.
Take an offline backup into a new destination:
scribe-server backup --data-dir ./data/storage-a --to ./backup-before-authEnabling auth preserves accepted history, but existing plaintext workspaces have
no authenticated owner: they still sync, but submitting needs a new working
copy made after logging in. This stage creates new working copies and keeps
work/alice-plain and work/bob-plain intact for comparison and recovery. Do
not copy their .scribe/_state directory into the new copies; reapply any
preserved local edits explicitly after syncing the new copies.
2. Add auth to the cluster
Look at the plan. It shows how the definition changes and every step:
scribe-ctl --config scribe-cluster.toml add auth --planScribe · local · add auth plan · auth variant · native runtime
...
ENDPOINTS
auth tls://127.0.0.1:7443
storage-a tls://127.0.0.1:7447
CHECKS
ok definition add auth rewrites scribe-cluster.toml with auth
...
DEFINITION CHANGES
@ scribe-cluster.toml
[clusters.local]
# What setup builds: storage, auth or gateway. Setup refuses services that do not match it.
- variant = "storage"
+ variant = "auth"
...
+ # Accounts, credentials and access decisions. TLS only.
+ [clusters.local.auth]
+ # A fixed port: credentials name this address.
+ listen = "127.0.0.1:7443"
+ data_dir = "data/auth"
+ # Written by setup and needed by every auth start. Back it up apart from the data.
+ pepper_file = "secrets/auth.pepper"
+ # Revision witness: keep it outside the data directory and its backups.
+ revision_file = "secrets/auth.revision"
+ # The bootstrap administrator's credential, written by setup. Keep it private.
+ admin_credential = "secrets/admin.credential"
...
- auth = false
+ auth = true
+ # The auth namespace this server's repositories belong to.
+ namespace = "studio"
+ # This server's own credential, issued by setup. Keep it private.
+ credential = "secrets/storage-a.credential"
...Then apply it and start the cluster:
scribe-ctl --config scribe-cluster.toml add auth
scribe-ctl --config scribe-cluster.toml upScribe · local · Auth added
rewrote scribe-cluster.toml; the previous definition is kept as scribe-cluster.toml.bak
created the storage and gateway CA in pki/ca
created the auth CA in pki/auth-ca
issued pki/auth/server.pem
issued pki/storage-a/server.pem
initialized data/auth; administrator credential secrets/admin.credential
storage-a: bound to namespace studio
server storage-a: registered in studio
storage-a: credential issued to secrets/storage-a.credential
scribe-monitor: created with its grants in studio
scribe-monitor: credential issued to secrets/monitor.credential
wrote CLUSTER-INFO.txt
ENDPOINTS
auth tls://127.0.0.1:7443
storage-a tls://127.0.0.1:7447
AFTERWARDS
- Start it with `scribe-ctl --config /home/you/scribe-demo/scribe-cluster.toml up`: auth first, then storage.
- Clients switch from tcp:// to tls://127.0.0.1:7447 (trusting pki/ca/ca.pem with --tls-ca) and log in through auth at tls://127.0.0.1:7443 (trusting pki/auth-ca/ca.pem with --auth-ca); plaintext connections are refused from now on.
- Nobody has access yet: add people and grants with scribe-auth and the administrator credential secrets/admin.credential (the Next commands in CLUSTER-INFO.txt).
- Workspaces created before auth have no signed-in owner: they still sync, but submitting needs a new workspace (scribe workspace create) made after logging in; bring unsubmitted work over by hand.
- Give clients pki/ca/ca.pem and pki/auth-ca/ca.pem; keep the CA private keys (ca-key.pem beside them) offline.
...
Scribe · local · running
SERVICE STATE PID ENDPOINT
auth running 17165 tls://127.0.0.1:7443
storage-a running 17166 tls://127.0.0.1:7447up starts auth first, then storage. The game repository and its history
are unchanged; storage now serves only TLS with sign-in.
What scribe-ctl did for you
-
Two certificate authorities. Servers prove who they are with TLS certificates.
pki/casigns the storage (and later gateway) certificates;pki/auth-casigns only auth's, so no storage or gateway key can ever pass as auth. Clients get the two publicca.pemfiles; the privateca-key.pemfiles are needed only to issue and renew server certificates and to rotate a CA: move them offline once you have what you need, and give their directory toscribe-ctl renew --ca-dir(forpki/auth-ca,--auth-ca-dir) when a certificate is due. These are server certificates only: people and servers sign in with credentials auth issues from its own private CAs (see identity). -
The auth database in
data/auth, created byscribe-auth init, with two files kept outside it:File Who reads it What to keep in mind secrets/auth.pepperAuth, at every start The password pepper; back it up apart from data/auth: without it no password can be checkedsecrets/auth.revisionAuth, at every start The revision witness: auth refuses to start from a database older than it, so a restored old copy cannot revive revoked credentials. Keep it out of data/auth's backupssecrets/admin.credentialYou, for scribe-authadministrationThe nonexpiring bootstrap administrator credential: auth's address, the auth CA, a device key and its certificate. There is no administrator password; never give this file to users -
The store joined a namespace.
scribe-server auth-initbounddata/storage-ato the auth namespacestudioin place (a bound store no longer starts without auth), and auth registeredstorage-aas a server ofstudio. -
Server credentials.
secrets/storage-a.credentialis the store's own identity towards auth (valid 365 days); it can check callers but cannot administer anything.secrets/monitor.credentialbelongs to thescribe-monitorservice account, a namespace reader thatscribe-ctl dfandrepo listuse to read inventory. Stage 4 renews both before they expire.
On Unix every credential file is written with mode 0600, and auth stores only a digest of each public key: private keys stay with their holders. The same steps by hand are bare metal, stage 2 and Docker Compose, stage 2; the certificates guide shows all four CAs, these two and auth's internal ones.
3. Check the new endpoints
scribe-ctl --config scribe-cluster.toml status
scribe --server tls://127.0.0.1:7447 --tls-ca ./pki/ca/ca.pem repo liststatus shows both services with tls:// endpoints. The repo list, made
without signing in, prints (no repositories): game is still there, but
nobody can see it until you add people and grants. A tcp:// connection is
refused.
4. Connect the auth administration CLI and add users
Save this as admin-env.sh in the deployment directory:
export SCRIBE_DEMO="$PWD"
auth() {
scribe-auth --admin-credential "$SCRIBE_DEMO/secrets/admin.credential" \
--state-dir "$SCRIBE_DEMO/admin-state" "$@"
}The credential file names the auth endpoint and its CA, so no other connection options are needed. Load the helper and verify administrator access:
. ./admin-env.sh
auth users listusers/admin fb162b07d0f4ee236b451a917262ec58 admin true true person
users/scribe-monitor 9c4bdb7a5d6ab38a6afeee3ea99741cd scribe-monitor true false serviceThis auth function is only a short wrapper around the scribe-auth CLI;
it is not scribe login. It can be loaded again in any terminal from the
deployment directory. admin-state holds durable pending requests, so an
interrupted administrative command can be retried with its original intent.
Create the password files empty and private first, so a password is never written to a file others can read:
mkdir -p passwords
(umask 077 && touch ./passwords/alice ./passwords/bob)
ls -l ./passwords # -rw------- eachThen open each in a text editor and enter a different password of 12–1024 UTF-8
bytes; a trailing newline is allowed. Most editors keep an existing file's mode;
repeat the ls -l afterwards to confirm. Then create the users:
auth users add alice
auth users password alice --password-file ./passwords/alice
auth users add bob
auth users password bob --password-file ./passwords/bob
auth users listusers add creates the identity; users password sets its login password. The
same password command later resets it and invalidates the user's existing
sessions. Neither user is an auth administrator, and neither receives a
credential file: people sign in with scribe login, which creates a device key
on their own machine.
5. Assign responsibilities
Authentication answers who someone is; grants decide what they may do. In this
exercise Alice manages the studio namespace and Bob starts as a reader of game:
- An auth administrator uses an administrator credential to manage users, credentials, server identities, and grants. That status alone gives no repository access.
- A namespace administrator can create repositories and has all repository permissions within that namespace.
- A repository reader can discover, inspect, and sync its history. A writer can also submit, branch, and take their own locks. A maintainer can additionally force-unlock and change the main branch's lock policy.
See the complete role model for the exact permissions.
auth namespace-admins set alice studio6. Log in as Alice and give Bob repository access
Save client-env.sh in the deployment directory:
export SCRIBE_DEMO="$PWD"
export SCRIBE_ENDPOINT=tls://127.0.0.1:7447
alice() {
scribe --config-dir "$SCRIBE_DEMO/clients/alice" \
--tls-ca "$SCRIBE_DEMO/pki/ca/ca.pem" --server "$SCRIBE_ENDPOINT" "$@"
}
bob() {
scribe --config-dir "$SCRIBE_DEMO/clients/bob" \
--tls-ca "$SCRIBE_DEMO/pki/ca/ca.pem" --server "$SCRIBE_ENDPOINT" "$@"
}Load both helpers and log in:
. ./admin-env.sh
. ./client-env.sh
alice login alice --auth tls://127.0.0.1:7443 --auth-ca ./pki/auth-ca/ca.pem
alice repo info gameLogged in as alice; session valid for 90 days.
info: game id=689a965e479c1decf32f3c9ca7e3f913 head=2 main=2403ffcaa1d1 storage-compression=lz4Enter Alice's password at the hidden prompt (for automation, add
--password-file ./passwords/alice). login creates a device key on this
machine and sends auth only its public key with the password; auth answers
with a certificate for the session (90 days by default). Before talking to
storage, the client obtains a short access certificate (one hour) from auth and
renews it on its own. The explicit CA files verify both endpoints: --tls-ca
the storage CA, --auth-ca the auth CA. An ordinary user can instead approve
the administrator-provided fingerprint using
remembered trust. Sessions are saved
under clients/alice and clients/bob here; normal installations use the
per-user configuration root.
Copy the full repository ID from repo info game and substitute it for
REPOSITORY_ID below, without angle brackets. Grants use namespace/id, not
studio/game:
auth grants set bob studio/REPOSITORY_ID --role reader
bob login bob --auth tls://127.0.0.1:7443 --auth-ca ./pki/auth-ca/ca.pem
alice workspace create game ./work/alice
bob workspace create game ./work/bob
(
cd work/alice
alice sync
)
(
cd work/bob
bob sync
cat Assets/Hero.txt
bob edit Assets/Hero.txt
printf 'hero mesh v3\n' > Assets/Hero.txt
bob submit -m "Authenticated hero update"
)Both authenticated copies contain hero mesh v2. Bob can read, but a reader
cannot submit:
scribe: permission denied; submit needs the writer role in repository 'game'; ask a repository administrator to grant it (forbidden)Change his existing grant to writer and submit the same edit again:
auth grants set bob studio/REPOSITORY_ID --role writer
(
cd work/bob
bob submit -m "Authenticated hero update"
)
(
cd work/alice
alice sync
alice log
)submitted change 3 (1 file, 1 of 1 chunks sent, 208 object bytes sent for 13 logical bytes, 2 server flushes)
...
change 3 branch main 2026-09-27T12:44:09.034Z bob 1 entry
Authenticated hero update
change 2 branch main 2026-09-27T12:43:33.029Z bob 1 entry
Hero v2
change 1 branch main 2026-09-27T12:43:32.472Z alice 1 entry
Initial heroThe new change is attributed to bob; --author cannot impersonate another
authenticated user. A grant update can take up to 30 seconds to affect new
admissions on existing connections; retry after that bound if needed.
7. See who can do what
scribe-ctl --config scribe-cluster.toml accessScribe · local · access
ACCOUNT KIND STATE SCOPE ACCESS DETAIL
admin person enabled auth administrator accounts, grants and credentials
admin person enabled credential bootstrap user never expires
alice person enabled namespace studio administrator every repository; creates repositories
alice person enabled sessions 1 signed in latest expires 2026-12-26
bob person enabled repository studio/689a965e479c1decf32f3c9ca7e3f913 writer
bob person enabled sessions 1 signed in latest expires 2026-12-26
scribe-monitor service enabled namespace studio reader every repository, including later ones
scribe-monitor service enabled credential scribe-ctl service-reader expires 2027-09-27Each privilege is its own row: admin administers auth (no repository
access), alice administers studio, bob writes to one repository, and
scribe-monitor only reads. access --user bob narrows it to one account.
Server credentials such as storage-a's are not listed there; auth credentials list shows every credential.
You now have a secure single-storage deployment. Keep it running for
3. Scale with a gateway, or stop it with
scribe-ctl --config scribe-cluster.toml down.