Keep your existing sign-in
What works when your app already signs people in with Supabase, Clerk, Auth0, Firebase or its own code, and you want BuildBase's other modules.
Your app already signs people in, with Supabase, Clerk, Auth0, Firebase or code of its own, and you want BuildBase for teams, plans, credits or email. BuildBase does not read your provider's tokens: there is no setting that tells it to trust a Supabase, Clerk, Auth0 or Firebase sign-in, and no way to look a person up by your provider's user ID. Every BuildBase module works on a BuildBase user and a BuildBase session. This page says what that leaves you, so you can choose before anything changes.
Stop and ask before migrating
If you are a coding agent: when the app already signs people in, stop and ask the person you work for which path below they want. Do not replace a working login on your own. It is the one change that can lock every existing user out.
Three paths
| Path | People sign in with | What BuildBase gives you | Status |
|---|---|---|---|
| 1. Move your users to BuildBase | BuildBase's hosted sign-in | Everything, including the React SDK's screens and hooks | Supported end to end. The path for an app made in Lovable, Bolt, Replit or v0 |
| 2. Your provider as a sign-in button | Your provider, through BuildBase's hosted page | Everything, as path 1 | Supported for a provider that speaks OpenID Connect |
| 3. Keep your sign-in, call BuildBase from your server | Your provider, as today | Teams, plans, credits and email, called from your own backend | Works through the API below. Not a packaged mode: you write and own the bridge |
1. Move your users to BuildBase
The command line tool reads the export Clerk, Auth0, Firebase or Supabase gives you and creates each person in BuildBase, with their email address, name, phone number, their old ID kept as externalId, and the Google, GitHub, Microsoft, Apple or LinkedIn sign-ins they used.
npx buildbase import --from supabase supabase-users.json --dry-runPasswords come with them. The import stores each person's old password hash, never the password itself, and a plaintext password is refused. At their next sign-in, people type the password they always had: BuildBase checks it with the old system's algorithm (bcrypt, argon2, Django's PBKDF2 or Firebase's scrypt), then stores it the way it stores every password and drops the old hash. Nobody is asked to reset. Auth0 leaves password hashes out of its export; ask Auth0 support for them before you plan the move. Someone whose hash cannot be checked is imported with no password and signs in with a magic link, an email code, a social provider or Forgot password?.
Sessions do not come across. Everyone signs in once more, and adds a passkey or two-step sign-in again if they had one.
Import your users from another service walks through it from the export to the first sign-in, and Importing users is the reference.
2. Your provider as a sign-in button
A custom OpenID Connect provider puts one more button on BuildBase's hosted sign-in page, for a provider such as Auth0, Okta or Keycloak. People press it, sign in on your provider's page as they do today, and come back signed in to BuildBase. Your provider stays the place their password lives.
It still goes through BuildBase's hosted page and its redirect, so it is path 1's sign-in with your provider behind one button, not a silent hand-off. The provider must publish OpenID Connect discovery (or be plain OAuth 2.0 with a user info address). Whether Supabase, Clerk or Firebase can act as that kind of provider for your project is for their docs to say: BuildBase's guide is written and tested against a standard OIDC provider, not against those three. An organization has one custom provider, and a plan can lock it.
3. Keep your sign-in, call BuildBase from your server
Your provider keeps signing people in, and your backend, which already knows who is signed in, opens a BuildBase session for that person and calls BuildBase with it. Three calls, all with an org API key that stays on your server:
- Create the person in BuildBase once, with
POST /api/users/importand a one-line file. Each answer row carries the BuildBaseuserId, also when the person is already there (skipped). Store it beside your provider's ID: BuildBase has no lookup byexternalId. - Open a session for them with
POST /api/v1/public/token/exchange. The key must have the owner or admin role to name another user. - Call BuildBase with that session, through the server SDK's
withSession()or thex-session-idheader.
import { randomUUID } from 'node:crypto';
import BuildBase from '@buildbase/sdk';
const serverUrl = process.env.BUILDBASE_SERVER_URL!; // https://api.console.buildbase.app
const orgId = process.env.BUILDBASE_ORG_ID!;
const apiToken = process.env.BUILDBASE_API_TOKEN!; // orgId:secret, owner or admin role
// 1. Once per person: create them in BuildBase, keep their BuildBase id.
export async function ensureBuildBaseUser(user: {
id: string;
email: string;
name?: string;
}) {
const line = JSON.stringify({
email: user.email,
emailVerified: true,
name: user.name,
externalId: user.id,
});
const form = new FormData();
form.append('file', new Blob([line + '\n']), 'user.ndjson');
form.append('source', 'supabase');
const res = await fetch(`${serverUrl}/api/users/import`, {
method: 'POST',
headers: { Authorization: `Bearer ${apiToken}` },
body: form,
});
const { rows } = await res.json();
if (!rows?.[0]?.userId)
throw new Error(rows?.[0]?.message ?? 'import failed');
return rows[0].userId as string; // store it with your user
}
// 2. Open a session for them. Reuse it for its life (here, one day).
export async function openSession(buildbaseUserId: string) {
const res = await fetch(`${serverUrl}/api/v1/public/token/exchange`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: apiToken,
userId: buildbaseUserId,
expiresIn: 86400,
}),
});
const { sessionId } = await res.json();
return sessionId as string;
}
// 3. Call any module as that person.
export async function spendCredits(sessionId: string, workspaceId: string) {
const bb = BuildBase({ serverUrl, orgId }).withSession(sessionId);
return bb.credits.consume(workspaceId, {
amount: 1,
idempotencyKey: randomUUID(),
});
}With the session, the person can create and join workspaces, see their plan, spend credits and do what the server SDK lists. Some calls need only the key, with no session: granting credits, reading a named person's plan and limits (BuildBase({ apiToken }), bb.entitlements.get({ user: '<userId>' })) and emailing a user.
Every field and refusal of the exchange is in opening a session as one of your users.
Which to choose
- An app made in an AI app builder, with few users so far: path 1. Everything in the AI app builders pages assumes BuildBase sign-in.
- Your provider is an OIDC provider and people should keep its page: path 2.
- The sign-in cannot move yet, and you need billing or teams now: path 3, knowing you own the bridge, and plan the move to path 1 or 2 when you can.
Things to watch out for
Path 3 leaves these to you, all checked in the code:
- No automatic user. A person who signs up with your provider does not exist in BuildBase until step 1 runs; the exchange answers
404 user_not_founduntil then. They count toward your plan's end-user limit like anyone else. - No sign-up events. A person created this way emits
user.imported, neveruser.registered, and gets no welcome or verification email. A workflow or webhook that waits for sign-ups does not run for them; listen foruser.imported. - A powerful key on your server. Only an owner or admin key may open a session for another user, so a leak of it is a leak of every user. Keep it out of the browser and out of your AI builder's frontend code.
- Never a recent sign-in. Nobody signed in to an exchanged session, so a change that asks the person to confirm it's them is refused with
403 reauth_required. It is listed among the person's sessions as opened by your team (method: 'app_team'), and a company that uses enterprise SSO can refuse these sessions for its people. - The React SDK's screens expect BuildBase's sign-in.
SaaSOSProvideris built around the hosted sign-in. ItsgetSessionoption restores a session your app hands it, so passing the exchanged session there may run its hooks without the hosted page, but that is inferred from the SDK's types, not documented or tested. Build this path on the server SDK first.
Next steps
- Set up sign-in, step by step - every sign-in guide, and the order to set them up in.
- Import your users from another service - path 1, step by step.
- Add a custom OpenID Connect provider - path 2.
- Server SDK overview - the calls path 3 makes with a session.