Auth and sessions
Private apps are admitted at the edge. An unauthenticated browser is redirected to auth.opencloud.ai, where a one-time email link or configured password approves or creates the central browser session and returns directly to the original app URL. Email-link access creates the session in the browser that requested the email, not the window that opens the message.
Do not build a second sign-in form inside a private app.
Agent-created accounts
MCP start_onboarding creates a provisional identity and retained project request for a new email, with no app or credential. The owner confirms the project-aware email by the deadline it states (24 hours by default). An owner without an organisation then explicitly sets up their company or accepts its invitation; until then the request reports organisation_setup_required. Each account is assigned to one company; signing in or changing email never creates or transfers that assignment.
If the email already exists, OpenCloud sends an approval request and withholds both the project and agent credential until the user confirms it. A GET of the email link only renders the review page. The explicit confirmation navigation must carry a short-lived, preview-bound HttpOnly cookie, so mail-security link prefetchers cannot approve the request.
After the user chooses the explicit confirmation action, OpenCloud establishes the normal HttpOnly session, consumes the one-time secret, and redirects the same browser directly to the non-secret owner launch page. That page waits for company setup when needed and opens the project after deployment. Company setup opens in a separate tab; return to the retained request and ask the coding agent to complete onboarding. The agent never receives the browser session. The CLI keeps account and app credentials in its protected OS credential backend.
The regular access page defaults to email-link access. Submit an email address, keep that window open, then open the 15-minute message and explicitly confirm. The email-opened window reports that confirmation is complete and can be closed. A short-lived, path-scoped HttpOnly browser claim lets the initiating window detect approval, create its own session, and continue to the requested OpenCloud page automatically. The email token never becomes a session in the mail window, and another browser cannot claim the initiating window's request.
The page can instead reveal a password field. A confirmed identity with a configured password signs in immediately. An unknown address creates an unconfirmed identity when registration is enabled and sends the same scanner-safe confirmation email. It also starts a provisional browser session that expires at the 24-hour confirmation deadline. There is no separate registration mode.
Each successful sign-in creates an independent browser session. Signing in on another browser or device does not end existing sessions; signing out ends only the session in that browser. A confirmed browser session has a fixed, non-sliding 30-day lifetime at both the OpenCloud broker and the underlying Auth session. Its one-hour access token refreshes during the final minute and each successful refresh rotates the server-held refresh token.
If Auth definitively rejects a refresh token before that deadline, OpenCloud revokes the unusable broker session and treats the browser as signed out. A public app continues anonymously, a private app redirects to Login, and the Login page renders normally. Timeouts and Auth service failures remain errors; they are not mistaken for a signed-out user.
Existing link-only users can sign in with an email link and set a password in global Preferences.
Passwords
Passwords must contain 8 to 72 characters. The central Auth service stores the password verifier; OpenCloud's application database does not store plaintext passwords or password hashes. A signed-in user can set or replace a password in global Preferences. The form includes matching guidance beneath both password fields. Changing a password does not disable email-link access, so the confirmed email remains an alternate sign-in and recovery path.
While a password registration remains unconfirmed, the user can create and work on their own apps but cannot approve connected clients, perform owner-only actions, or use private apps granted by another owner. If the deadline passes before confirmation, OpenCloud revokes provisional grants and browser sessions and pauses every app owned by the unverified user. HTTP runtime traffic, Functions, and cron stop. The app address shows a verification-required page with a resend action, while data, files, deployments, secrets, and backups remain preserved. Submitting the verification form resumes all affected apps and active cron schedules.
Delete an account
A signed-in person can delete their account from Delete account at the bottom of global Preferences. Deletion is immediate and permanent; there is no undo window. It needs a sign-in from the last 10 minutes and the account email typed as confirmation.
Deletion signs the account out everywhere, revokes its CLI, MCP, app access and Agent credentials, stops its running Agent work and takes its apps offline at once. A durable operation then deletes the account's apps (data, files, Functions, backups, custom domains and email history), its Agent conversations, workspaces and traces, its provider and integration connections and its push subscriptions, and finally the sign-in identity and account record. A confirmation email goes to the old address. Audit history is kept without the email address. Database backups still inside their write-once retention window are erased when that window ends, at most 30 days later.
Apps that belong to an organisation stay with it; the person's membership is removed, and they can rejoin that organisation only through a new invitation. An organisation's only administrator must first make another member an administrator. App users who signed in to a deleted account's apps keep their own accounts.
Render the current user
const user = await opencloud.auth.currentUser();
if (!user) {
signInLink.href = opencloud.auth.signInUrl();
renderSignedOut();
} else {
renderIdentity(user.displayName ?? user.email ?? user.id);
}The SDK keeps token and expiry state private and refreshes through the HttpOnly broker cookie. Never decode JWTs, copy access tokens into application state, or persist token material.
Public apps
A public app may have either an authenticated or anonymous visitor. Use auth.currentUser() to adapt the UI. Data reads use the current user when present and can follow deliberately public RLS; data and file writes still require a user. Point a protected action at auth.signInUrl() rather than constructing the central Auth URL or passing a return target yourself.
To accept a submission from a signed-out visitor, such as a sign-up, order or booking, call an access: public Function. It validates the input and hands it to a queue with jobs.enqueue, and an access: system queue consumer saves it under auth.is_system() policies for both insert and select. Do not add an anonymous insert policy instead: anonymous writes are refused, and create reads the new row back. Confirm the submission as soon as the Function accepts it: the queue saves it moments later, so the page should not wait for the saved row. Keep staff views behind auth.is_app_admin() policies. Before publishing, test the visitor's journey with anonymousPage, the signed-out visitor in development tests. Once the app is live, check it signed out with opencloud app request <app-id> /.
Private authorization
Private app access is separate from identity. A valid OpenCloud account receives HTTP 403 when it has not been granted access to that app. The platform applies this edge check before serving the frontend or runtime APIs.
Signed-in platform administrators are the explicit exception: they may open any active deployed app with their own identity for safety, abuse, or security review. Review access does not add an app membership or grant owner or builder control. Runtime interactions still follow the app's row-level security, Files, Function, integration, and AI policies, and secret values remain unavailable.
Programmatic private-app access
The verified current owner can create an app access token from the app's Access screen, the owner API, or full MCP. Store the one-time oc_app_... value in a secret manager. A client can then call any current or future authenticated runtime route with only that Bearer credential; the edge injects infrastructure credentials internally:
printf 'Authorization: Bearer %s\n' "$OPENCLOUD_APP_TOKEN" | \
curl --fail-with-body --header @- \
"$APP_URL/rest/v1/items?select=id,title"The token acts as the owner for PostgREST RLS, database RPCs, Functions, managed Files, Realtime, and platform-owned runtime endpoints. It is bound to one deployed app, is revalidated on every new request, and cannot manage apps, deployments, secrets, backups, or any other control-plane resource. Use POST /_opencloud/api/session when a client library specifically needs a short-lived JWT and semantic same-origin runtime paths; the response has no refresh token or infrastructure key.
The platform also exposes a short-lived owner-approval request endpoint for app-scoped clients. Public CLI v3.19.0 exposes it as app access-token request; owner sessions may also list, create, and revoke tokens with the adjacent app access-token commands. CLI creation uses a one-time reveal link and never prints the runtime secret.
Apps that manage their own sign-in
Some apps authenticate their own users instead of OpenCloud accounts, for example the API behind a mobile app. Pin SDK 2.7.0, make the app public, and serve the API from access: public Functions, optionally behind Function routes. OpenCloud admits these requests anonymously; the Function decides who the caller is.
- Read the client's credential from
headers.authorizationis present only when it is not one of OpenCloud's credentials. OpenCloud never treats it as identity, sends it to runtime admission, or forwards it to the database, and it works the same on a custom domain. It also arrives from a browser signed in to OpenCloud, such as the development preview, verification browsers, and an owner testing their own app; OpenCloud then admits that call anonymously too. To use the OpenCloud user and an app credential together, send the credential in a custom header. This needs a public app: on a private app, and its development hosts, a signed-in browser's session replacesAuthorization. - Verify it with your own code: a token your sign-in Function signed with a key from
secrets, or an identity provider's ID token checked against its published keys with Web Crypto. - Then act as that user with
data.asAppUser(id). Its database token names the app and your user ID, lasts at most five minutes, and is refused if a client ever presents it.
import { defineFunction, errors, schema } from "@opencloud/server";
export default defineFunction({
input: schema.object({}),
handler: async ({ headers, secrets, data }) => {
// verifySession is your app's own token check.
const userId = await verifySession(
headers?.get("authorization"),
secrets.require("SESSION_SIGNING_KEY"),
);
if (!userId) throw errors.unauthorized("SIGN_IN_REQUIRED", "Sign in again");
return data.asAppUser(userId).table("notes").list({ limit: 50 });
},
});Row-level security sees the user through auth.app_user_id():
create table notes (
id uuid primary key default gen_random_uuid(),
owner_id text not null default auth.app_user_id(),
body text not null
);
create policy notes_owner on notes for all
using (owner_id = auth.app_user_id())
with check (owner_id = auth.app_user_id());Rows that only app-user policies allow stay invisible to anonymous Data API requests and to OpenCloud users, so do not add an anonymous policy to reach them. A sign-in Function can keep its own records the same way: act as an ID derived from the request, such as login: followed by the normalized email, to read that sign-in record, then use the account's stable ID for its data. With an identity provider, the provider and its subject, such as apple:<sub>, can be the user ID.
auth.uid()anduserstaynull, andauth.is_system()andauth.is_app_admin()are false. OpenCloud-account policies never match an app-managed user.- A private app admits only OpenCloud accounts, so
asAppUserfails there withAPP_USER_IDENTITY_REQUIRES_PUBLIC_APP. - App-managed users have no per-user Files, Realtime, Web Push or calling-user integrations; those use OpenCloud accounts.
- Use your own token format. A bearer token issued by OpenCloud, or one that claims one of OpenCloud's issuers, is always treated as OpenCloud authority.
- Cookies are not forwarded and the client IP is not exposed. Throttle sign-in attempts per account in your own data.
- Development previews forward client headers too, and
asAppUserthere acts only on the session's isolated data.
Session failures
nullin a public app is normal.nullafter a private app rendered indicates an expired or unavailable brokered session; show retry/sign-in UI.OpenCloudErrorwith codeAUTH_REQUIREDmeans an authenticated SDK operation cannot proceed.- Never bypass the high-level SDK or fall back to anonymous raw requests for a user-private mutation.
