QUESTPIE
AdminAuth

Identity tables

Better Auth's own tables arrive as ordinary collections. Four of them refuse generic CRUD outright, the user table is narrower than it looks, and the session endpoints never hand back a reusable token.

View markdown

starterModule contributes these, and adminModule pulls starterModule in. So every admin app has them whether or not it asked.

CollectionGeneric CRUD
userAdmins manage all. Others see their own row.
sessionClosed.
accountClosed.
verificationClosed.
apikeyClosed.

The four closed tables

session, account, verification and apikey set read, create, update and delete to false. That holds even when your app's defaultAccess opens everything to any signed-in user. A rule written on the collection wins over the app default.

Their secret columns are shut a second time, at field level.

CollectionFields closed again
sessiontoken
accountpassword, accessToken, refreshToken, idToken
verificationvalue
apikeykey

Better Auth still writes session, account and verification. It goes through its own database adapter, not through collection CRUD. These rules never see it, so closing the collection does not break sign-in.

apikey is the odd one out. QUESTPIE ships the table, but no module turns on an API-key plugin. Nothing writes it until you add one.

The user table

OperationWho
readAdmins read every row. Anyone else reads their own.
createAdmins.
updateAdmins, and the signed-in user on their own row.
deleteAdmins.

Read for a non-admin is not a yes or a no. The rule returns the filter { id: userId }, so a list narrows to one row instead of failing. With no session it returns false.

Seven fields are admin-only on create and update, whoever owns the row.

FieldWhy
email, emailVerifiedBetter Auth owns the identity.
roleOtherwise anyone promotes anyone.
banned, banReason, banExpiresModeration is not self-service.
imageA hook writes it from avatar.

So a signed-in person can edit their own name and their own avatar. Nothing else on the row. Changing an email or a password is not a collection write at all. Use Better Auth's own flows.

avatar is an upload to the assets collection, and it stays open. A beforeChange hook resolves it and writes the asset URL into image, after the field rules have run. So the two never drift.

Sessions on the wire

Two Better Auth endpoints do not reach Better Auth unchanged. Both sit under /auth, below your handler's base path. That is /api in the starters.

QUESTPIE projects GET /auth/list-sessions before it goes out. The response never carries a reusable bearer token. Each row's historical token property holds the session row id instead, and sessionId holds the same value. The list puts the current session first, sorts the rest by newest, and cuts to 100 rows. Each row also carries isCurrent.

POST /auth/revoke-session takes that opaque id. The server lists your own sessions, finds the matching row, and only then calls Better Auth with the real token. Pass a raw bearer token, or another user's id, and you get { status: true } and nothing happens.

Do not loosen these to make a screen work

An account screen that needs more than this wants a route of its own, running the check in server code. Opening session or account to read exposes tokens and password hashes to every signed-in caller.

Where each topic lives

TopicPage
Who may sign in to the panelAuthentication
How field rules and filters resolveAccess control
Extending a collection a module shipsModules
The assets collection behind avatarUploads

On this page