Permissions and write-gating
Two independent settings decide who can write what in your app: the
glow's auth mode (a deploy/console setting — is this glow open to the
world, restricted to an allowlist, or open self-serve?) and each
signed-in user's permission (admin / user / viewer). This guide
is about designing a UI around the combination of the two — which is
where a static-page mental model tends to go wrong.
The reference documents each layer on its own: the
auth-mode surface matrix
says what every surface does for each mode-and-sign-in state, and
backlit.auth plus
auth.me()
carry the permission field. This guide links to those rather than
restating them, so it can't drift out of sync with the rules they define.
Two dials, not one
- Auth mode is set on the glow, not in your app code. It decides whether a visitor arrives signed in, arrives anonymous, or can't arrive at all until they sign in. The three modes and exactly which surfaces each mode's visitors can reach are the Public, Private, and Accounts matrix — read that as the authority; this guide won't reproduce its cells.
- Permission is a property of a signed-in user, surfaced as the
permissionfield onauth.me(). It's what separates an editor from a read-only viewer after they're signed in.
The trap is treating "signed in" as "can write." It isn't: a signed-in
viewer reaches the app but still can't write shared data. Auth mode
gets the user in; permission decides what they can do.
The server is the gate — your UI just mirrors it
Every write surface enforces its own rule server-side, and a blocked
write throws a specific code. You don't invent the gate; you read it
off the reference and reflect it in the UI so the user isn't surprised
by a rejection. The authoritative gate for each store lives with that
store:
backlit.data— the glow-shared store. Its writes need a signed-in non-viewer.backlit.records— public-read, owner-write. Any signed-in user (aviewerincluded) can create and edit their own entries.backlit.capture— write-open, admin-only read. Anyone (even anonymous) can write a capture; only anadmincan read them back.
The two error codes you'll design against are the forbidden and
unauthenticated rows in the
error-code table — one
means "signed in, but not allowed," the other "no user session at all."
Consult the table for which call raises which; the point here is only
that a good UI never lets the user trip them.
The recipe: pick the store by who's allowed to write
Instead of memorizing the matrix, run each thing your app writes through two questions — and a default:
- Can a signed-out visitor write it? If yes, that's a
capture— a contact form, a telemetry ping, an anonymous submission the page can't read back. It's the only store whose create works with no session. - Should each signed-in user own their own entries, viewers
included? If yes, that's
records— comments, posts, profiles. A freshly self-served Accountsviewercan post torecordsbut not to shareddata, which makesrecordsthe natural home for user-generated content on an open glow. - Otherwise it's shared state everyone sees but only privileged users
edit — that's
data, gated to a signed-in non-viewer.
Choosing the right store is the permission design. If you find yourself
wishing a viewer could write shared data, you probably wanted
records.
Gate the control, not just the call
Because permission is readable before any write, branch your UI on it
so a user never sees a control they'd be rejected for using. The
canonical move is to disable the write affordance for a viewer:
const me = await backlit.auth.me();
// Anonymous visitor: no user session at all.
if (me === null) {
saveBtn.hidden = true;
// On an Accounts glow you can offer sign-in here; see below.
} else {
// Signed in — but a viewer can't write shared data. Reflect that.
const canWriteShared = me.permission === "user" || me.permission === "admin";
saveBtn.disabled = !canWriteShared;
adminPanel.hidden = me.permission !== "admin"; // capture review, user list, …
}
This is a mirror, not the enforcement — the server still rejects a
viewer's shared-data write no matter what your buttons do (see
auth.me()
for why permission exists to branch ahead of the write). Reflecting it
in the UI is purely so the user isn't handed a button that will fail.
Anonymous visitors and auth.login()
On a Private glow you never handle a signed-out state inside your
page — a visitor without a session is redirected to sign in before your
JavaScript runs. On a Public or Accounts glow your code does
run for anonymous visitors, so auth.me() returning null is a real
state to design for. What sign-in does from there differs by mode, and
the auth-mode matrix
is the authority on it — in short, an Accounts glow lets any visitor
create an account, while a Public glow only admits allowlisted users
(e.g. an admin unlocking privileged controls).
const me = await backlit.auth.me();
if (me === null) {
// Anonymous on a Public/Accounts glow. Offer sign-in and route
// anonymous input through capture in the meantime.
loginBtn.onclick = () => backlit.auth.login(location.pathname);
}
Use auth.login()
to send the visitor to sign-in; anything they need to submit before
signing in belongs in
capture,
the one store an anonymous session can write.
A worked shape: an open glow with mixed access
Picture an Accounts glow — a community board. Anyone can read it; a signed-out visitor can leave a "contact the organizers" note; a signed-in member posts entries others can see but not edit; an admin curates a shared banner and reviews the contact notes. That maps cleanly onto the three stores and the two dials:
- The contact note is anonymous-writable →
capture.create. No session required, and only an admin can read the notes back. - A member's post is owned content a
viewermay create →records.*. Self-served accounts land asviewer, and that's fine here becauserecordsdoesn't gate creation on the shared-data permission. - The shared banner everyone sees but only staff edit →
data.*, with the edit control gated onpermissionbeinguser/admin.
Each store's own reference section
(data,
records,
capture)
has the exact method signatures and the precise gate; this guide's job is
only to help you pick the right one and reflect its gate in the UI.