onvibe.run

← All docs

Managed End-User Auth

Let the platform handle sign-in for your app's users. You set an access policy; the platform serves the login/signup pages, stores the accounts and sessions, and blocks unauthenticated visitors at the edge — they never reach your app, so bots and anonymous traffic don't consume its resources or wake it up.

This is for the end-users of your app (the people who visit it), not for your onvibe account. Don't build your own login tables for the common case — use this.

Set the policy

The policy is configuration: it lives in onvibe.json, alongside the allowlist and the public paths. Change it with the config cycle — get_config → edit → deploy (see onvibe://docs/config):

{
  "version": "<keep as downloaded>",
  "access": { "policy": "signup" }
}
policy who can access
public anyone, no login (the default)
password one shared password protects the whole app — no user accounts
signup visitors must register and sign in; open registration
allowlist only emails you pre-authorize can register and sign in

Password mode is the one exception, because the password is a secret and secrets never go in the config file: set it with set_access_policy({ project_id, policy: "password", password: "..." }) — the only tool that still writes access config. Visitors enter the shared password at /auth/login and are let in; there are no accounts and no currentUser() identity. Changing the password signs everyone out.

For allowlist, the authorized emails are part of the same file — the list is complete, so deploying it also revokes anyone you removed:

{
  "version": "<keep as downloaded>",
  "access": { "policy": "allowlist", "allowlist": ["ana@example.com", "bo@example.com"] }
}

The login pages are automatic

When the policy is signup or allowlist, the platform serves sign-in and registration at /auth/login (and /auth/signup, /auth/logout). You do not route these paths — the platform intercepts exactly those three, before the request reaches your app. Everything else (including other /auth/* paths like /auth/profile) goes to your app as normal. This reservation applies only while the app has a policy other than public: a public app can use /auth/login for its own route freely. So: if you enable managed auth, don't build your own /auth/login, /auth/signup or /auth/logout.

An unauthenticated request is redirected to the login page without ever hitting your app. That's the whole point: no wasted resources on traffic that isn't allowed in.

Branding: logo + title

The login, signup, password and reset pages show your app's logo and title, so they look like part of your app rather than a generic gate:

Read the signed-in user

The platform injects the identity as signed request headers. Use currentUser() from ./.onvibe/helpers.ts, which verifies the signature for you (a request that didn't come through the platform gate is rejected):

import { currentUser } from "./.onvibe/helpers.ts";

export default async function handler(req: Request): Promise<Response> {
  const user = await currentUser(req);   // { id, email } | null
  if (!user) return new Response("Unauthorized", { status: 401 });

  // user.id is a stable, immutable key — use it (not the email) to key this
  // person's rows in your database.
  return new Response(`Hello ${user.email}`);
}

currentUser() returns null for anonymous requests, for cron triggers, and for any request whose identity header isn't validly signed. Never trust a raw X-Onvibe-User header yourself — always go through currentUser().

Note: under a plain signup/allowlist policy the gate is all-or-nothing — anonymous visitors are redirected to login before your handler runs, so currentUser() is never null and the 401 branch above is effectively dead. It becomes reachable only on public paths (below), where anonymous visitors are allowed through.

Mixed public + private apps (public paths)

Many apps need a mix: a creator signs in to CREATE something, but anonymous visitors PARTICIPATE via a shared link (Doodle, RSVP, public polls, feedback boards). Keep the app on a signup or allowlist policy and open just the visitor-facing routes:

{
  "version": "<keep as downloaded>",
  "access": { "policy": "signup", "publicPaths": ["/", "/d/*", "/api/*/vote"] }
}

Manage accounts

list_app_users({ project_id })              // email + signup date (never passwords)
create_app_user({ project_id, email, password? })   // create an account directly
update_app_user({ project_id, email, new_email?, password? })  // rename / reset password
remove_app_user({ project_id, email })      // deletes the account and signs it out everywhere
create_password_reset({ project_id, email })  // one-time reset link to share (valid 7 days)

Importing users from another auth system

To migrate an existing user base into managed auth:

Self-service "forgot password": when the platform's system email is configured, the login page shows a Forgot password? link — the user enters their email and gets a reset link by email, no action needed from you. You can still mint links yourself with create_password_reset (e.g. for imports, or if email isn't configured).

Notes

Read this page as Markdown (best for LLMs) · plain text
onvibe.run · home · all docs