Auth levels
Shipped
Turbobase supports several ways to authenticate to your instance, each producing a session that carries the same kind of capability and scope information so that row-level security enforces access uniformly no matter how you got in.
Ways to sign in
Section titled “Ways to sign in”- OAuth (hosted sign-in) — the normal way a person signs in. They authenticate against the instance’s hosted sign-in, are matched to an invited user by verified email, and get a session. Access is invite-only: an authenticated identity that was never invited gets no session.
- Email and password — the built-in sign-in form, with an optional second factor (see below).
- Passkey — a user enrolled with a passkey can sign in directly with it.
- Personal access token — a script or the CLI presents a PAT as a bearer; it resolves to the owning admin and that admin’s live role. A token carrying the
mcp:datapermission reaches the data MCP too, under that same admin’s row-level permissions; reading is all it may do there unless it also carriesmcp:write, which an administrator grants alongside the read permission and never instead of it. A token carryingrest:readreaches the instance’s rows over the ordinary web interface as well — the batched read atPOST /v1/batchand an attachment’s bytes — andrest:writebeside it reaches the write routes, each request running as the owning admin for the length of that request. The two pairs are independent: neither implies the other. - App exchange — a Turbobase app exchanges a handoff for a session so it can act on the user’s behalf.
- MCP — an MCP client presents either a per-user OAuth access token or a PAT carrying
mcp:data. Either is bridged to a short-lived session so the same RLS fires for the client as for the app; the session is minted inside the request and never handed back. Changing data through it needs the instance’s MCP write setting on, and a second permission that depends on the credential: a token needsmcp:write, and a person’s own connected client needs the write permission its owner gave it on the approval screen or from Account → Connected apps. Neither is granted by default. - Native app (iOS, Android, desktop) — an app your team builds signs a person in through your instance’s own OAuth server, the same sign-in they see in a browser. The app registers itself, sends the person to your instance to sign in and approve the app by name, and receives that person’s own session as a token. It then reads and writes exactly what they may — no more — and the token expires on its own. Nobody new can be signed up this way: access stays invite-only, so a person the instance has never invited gets no session however they authenticate. Each person retracts an app from /settings/account → Connected apps, after which it cannot renew and must ask them again. See building a native app below.
- Staff impersonation — managed-service staff can act as a user in your instance for support. An impersonated session is short-lived and stamped with the staff member’s identity, so every action it takes is attributable back to a real person, not just to the borrowed user.
- Deploy — extension deploys authenticate with a deploy-scoped PAT.
Bring your own identity provider
Section titled “Bring your own identity provider”By default people sign in against the instance’s hosted sign-in. Administrators can also wire your own identity provider under Settings → Sign-in — connect a Google Workspace domain or any generic OIDC provider, with a copy-paste redirect URL to register on the provider’s side — so your team signs in with the accounts they already have. Access stays invite-only regardless of provider: an identity that was never invited gets no session.
Building a native app
Section titled “Building a native app”A mobile or desktop app your team builds signs people in against your instance directly — there is no separate sign-in to build and no shared key to ship inside the binary.
Register the app. POST /oauth/register with the callback the operating system routes back to it. The callback must be an https URL on a domain your app claims — an iOS universal link or an Android app link — because that claim is what proves the callback belongs to your app. A custom scheme such as com.example.app:/cb is refused, and it is what most OAuth libraries reach for first, so set the redirect explicitly. A desktop or CLI client may instead register http on 127.0.0.1 or localhost, on any port. The registration takes no credential and returns a public client id; there is no client secret, and PKCE is required.
Claim a callback on your instance. If the native app belongs with a hosted app on your instance, declare it in that app’s public/turbobase.json — "native": { "ios": { "appIds": ["<TeamID>.<bundle id>"] } }, and/or "android": { "packages": [{ "name": "<application id>", "sha256": ["<signing-certificate fingerprint>"] }] } — and deploy. Declaring is not enough on its own: your instance names only the apps that are also on its trusted native apps list, which Turbobase sets for your instance on request and which no deploy or app can change. Ask for each iOS app id, and each Android package with its signing-certificate fingerprint, to be added. Once both are in place your instance serves /.well-known/apple-app-site-association and /.well-known/assetlinks.json naming the app, with its callback at https://<your-instance>/oauth/app-callback/<app slug>. That URL is the callback to register. On iOS, add applinks:<your-instance host> and webcredentials:<your-instance host> to the app’s associated domains and target iOS 17.4 or later, the first release whose sign-in sheet catches an https callback. The claim follows the version that is live: a preview claims nothing until promoted.
Trust only apps you sign. Only the iOS universal link is limited to that one callback path. webcredentials lets the app use your instance’s passkeys and saved passwords, and an Android app link lets the app open any link on your instance, including password-reset and invite emails. Removing an app from the trusted list takes effect on your instance within a minute, but Apple and Android devices cache the file, so a phone can keep honouring the old one for hours.
Register at run time, not at build time. A registration nobody signs in on lapses after an hour, and one that is used lasts thirty days from its latest sign-in, so a client id baked into a build stops working. Register on first launch, keep the id on the device, and register again when /oauth/authorize or /oauth/token answers unauthorized_client.
Ask for the right thing. Send resource=https://<your-instance>/rest/v1 on /oauth/authorize and repeat it on /oauth/token. That is what says “this app wants the person’s data”, and it is what the token you get back is good for. Omit it and you get the MCP server instead. /.well-known/oauth-authorization-server lists under resources_supported whichever of the two your instance is actually serving — the data one always, the MCP one only while the MCP server is switched on — so a client that reads it never asks for something it would then be refused.
What the person sees. Your instance’s own sign-in, then a card naming your app and what it is asking for. They approve it once.
What the app holds. That person’s own session, as a bearer token. Send it to /rest/v1 (as both apikey and Authorization: Bearer), to POST /v1/data/:recordType, to POST /v1/batch, to POST /v1/stock and to GET /api/account/picture/:userId, exactly as a browser app does. It carries their capabilities and scopes and nothing else, and it lasts eight hours.
Drawing people’s pictures. GET /api/account/picture/:userId answers with the picture that person has set, so your app shows the same faces the web app does instead of initials. Anyone signed in to your instance can read anyone’s, which is what lets a list of people show a face beside each name. It carries an ETag, so send it back as If-None-Match and a re-draw costs you a 304 and no image. A person who has set no picture is a 404 — draw your own initials or glyph for that. Setting or removing a picture is still the web app’s /settings/account → Profile picture; the bearer reads, it does not write.
Renewing it. The token response also carries a refresh_token. Keep it, and when the eight hours are up send it with grant_type=refresh_token and the same client_id — the person sees nothing. When a renewal hands back a new refresh_token, keep that one and discard the one you used; when it hands back none, the one you just sent is still the one to keep. Renewal works for thirty days from the sign-in, and using it does not extend that: on day thirty-one the renewal is refused with invalid_grant, and the app signs the person in again. Treat that refusal as the signal to start the flow at /oauth/authorize, not as an error to retry.
Turning it off. The person retracts the app under /settings/account → Connected apps. It cannot renew the token it already holds — the next renewal is refused — and it has to ask them again before it can obtain a new one. The token in its hands is not cancelled at the click; like any session it runs out on its own, within the eight hours above.
Passwords and second factors
Section titled “Passwords and second factors”Password sign-in is email-first: you enter your email at /login, then your password on the next step. If your account has a second factor enrolled, the password alone isn’t enough — you land on /login/mfa, which shows whichever factors you have: a field for your authenticator (TOTP) code, a Use a security key button for a passkey, an email me a code option, or any combination. The email code doubles as a recovery path, so a lost authenticator or passkey doesn’t lock you out.
Forgot your password? /login/reset-password emails you a reset link — the response is deliberately generic, so the form never reveals whether an address is on file. The link lands on /login/set-password, where you choose a new password against a live checklist of the policy: at least 12 characters, with an upper-case letter, a lower-case letter, a digit, and a symbol.
Managing your sign-in methods
Section titled “Managing your sign-in methods”Each user manages their own security surface at /settings/account, under four tabs — Profile (the picture on your avatar), Account access (how you sign in), Connected accounts, and Connected apps:
- Password — change it; your current password is required.
- Passkeys — enroll a passkey through the browser’s native prompt, see each enrolled passkey with when it was added and last used, and remove one.
- Authenticator (TOTP) — enroll by scanning the generated secret into an authenticator app and confirming a code.
- Email code — turn on receiving your second-factor code by email; it also serves as a recovery factor when an authenticator or passkey isn’t to hand.
- Connected apps — on the page’s Connected apps tab: see everything you have connected to your account — a phone app your team built, an MCP client, or both — and revoke any of them. Each row says which it is. The list is your own connections only; you can never see or revoke another user’s.
What a session carries
Section titled “What a session carries”Every session encodes:
- The user’s role (admin or standard) — administrators bypass RLS.
- The user’s capabilities — the verb × record-type operations they may perform.
- The user’s scopes — the dimensional restrictions limiting which rows they can see and touch. A restriction is a write boundary as well as a read filter: creating or editing a record that names a dimension outside the session’s own scope is refused before the statement runs, so nobody commits a row they would then be unable to find.
- The user’s content grants — the exact message-history channels the role they are acting as may read.
Because these are carried on the session itself, the database decides most of what a request may touch from the session alone, and the same decision holds for the app, the PostgREST API, and MCP alike. Two things are checked live on every statement instead:
- Whether the session is still alive. A person who signed out, was suspended, or had the role they are acting as edited stops reading and changing your instance’s records straight away, from every client — not at their next sign-in and not when their session runs out. See Users and permissions for what each of those does, and App auth wiring for what an app sees when it happens.
- Content grants. Each request checks them against the role as it stands now, so a channel removed from a role, or a role taken away, stops being readable immediately.
Sessions are time-limited and expire.
How long people stay signed in
Section titled “How long people stay signed in”A browser session lasts 1 day unless an administrator changes it, and it slides — an active person is kept signed in, so the length is really the stretch of inactivity that ends the session.
An administrator can set it in two places, in whole days, and the longest a session may last either way is 30 days:
- Instance-wide — Settings → Sign-in → Sessions, under Keep users signed in for. This is the default everybody gets.
- Per role — the session-length field on the role editor. A role’s own value wins for its holders, whether it is longer or shorter than the instance default. Leaving it empty means the role inherits the default.
Both are in seconds, and neither accepts anything below 1 hour or above 30 days. Changing either reaches people who are already signed in: the new length is applied the next time their session renews, so shortening a role takes effect without anybody having to sign out.
Weigh a long session against where it is used — a browser left open on a shared machine stays signed in for exactly as long as you set. Lengthening it suits a role whose people only read.
Two things never follow this setting: a staff impersonation session and a view as session always last ten minutes, however long an instance’s own sessions are. A native app’s token has its own lifetime too, described above, and so does an identity provider you have connected — if your provider ends its own session sooner, your people will be asked to sign in again at that point regardless of what is set here.
Realm isolation
Section titled “Realm isolation”Instance sessions are issued and verified only for your instance. The managed-service staff and customer-console identities are entirely separate realms — they never share a session, secret, or cookie with your instance. Staff impersonation is the one bridge, and it’s deliberately short-lived and fully attributed.
Related
Section titled “Related”- Users and permissions
- Access tokens
- Audit log — every sign-in and session event is recorded