BuildBaseBuildBase

Signing in as a user

Open your app as one of its users from the console, read-only by default, for 5 to 60 minutes, with the start and an early end audited.

When a customer writes in to say the button is not there, the quickest way to find out why is to look at their screen. Sign in as this user, on the user's page in the console, opens your app in a new tab signed in as them. The session is read-only unless the owner says otherwise, it ends on its own, and its start goes into the security audit log.

Step by step, with screenshots: Sign in as a user.

Before you start

Sign in to the console with a role that can create users and read auth settings. Your client needs a redirect URL for the session to arrive at. An API token cannot start one: it has nobody to hold the tab.

Starting one

The dialog asks for four things:

FieldWhat it does
AppThe client to sign in to. An agent client is refused
Open atOne of that client's redirect URLs
MinutesHow long the session lasts: 5 to 60, and 15 unless you change it
Read-onlyOn by default. Only the owner role can turn it off

Your app needs nothing new for this. The console mints an ordinary sign-in code for that redirect URL, and your backend exchanges it at POST /api/v1/auth/token exactly as it does after a real sign-in. The session it makes:

  • ends when its minutes run out, however long your sessions normally last
  • is never a recent sign-in, so it cannot confirm it's you or pass a change that needs one
  • does not count the person as an active user

A blocked user cannot be signed in as. Unblock them first.

Read-only

A read-only session reads and cannot change anything through BuildBase. Every write to the session API, /api/v1/public with any method but GET, HEAD and OPTIONS, answers 403 read_only_session. Signing out still works, for that session only: POST /api/v1/public/logout with { all: true }, which signOut({ everywhere: true }) sends, ends the read-only session and leaves the person's own sessions alone. The SDK's screens show that refusal's sentence, and isReadOnlySession(error) recognises it in your own code.

Your own data is your backend's call. BuildBase cannot see your database, so read-only stops at BuildBase. The profile tells your backend who is looking: GET /api/v1/public/profile, and the user in the code exchange's answer, carry impersonation: { actorName, readOnly, expiresAt } for as long as the session lasts. Refuse your own writes while readOnly is true.

With read-only off, everything the owner does in that tab is done as the person. That is why only the owner role can choose it.

Except how they sign in. Their email address, phone number, username, password, second step, passkeys and linked accounts stay theirs in every sign-in-as session, read-only or not: each of those changes answers 403 impersonation_sign_in_locked. A wrong current password typed in that tab does not count toward the person's own sign-in lockout either.

The banner

For as long as the session lasts, the SDK pins a bar to the bottom of the window: "You are viewing as Ada. Grace signed in as this person, read-only until 3:15 PM.", with Exit, which signs that tab out. SaaSOSProvider renders it already. To put it in your own layout instead:

import type { ReactNode } from 'react';
import { ImpersonationBanner, SaaSOSProvider } from '@buildbase/sdk/react';

export function Providers({ children }: { children: ReactNode }) {
  return (
    <SaaSOSProvider
      // ...serverUrl, orgId and auth, as before
      ui={{ impersonationBanner: false }}
    >
      <ImpersonationBanner fixed={false} />
      {children}
    </SaaSOSProvider>
  );
}

useSaaSAuth().session.impersonation holds the same three fields.

Ending one early

End sign-in-as sessions, on the same user page, signs out every session anyone opened as that person from the console, wherever it is open. The person's own sessions are not touched. Each early end from the console is written to the security audit log too. Exit on the banner, and a session running out, are not.

Audit

RowWritten when
impersonation.startedA session starts. It records the client, whether it is read-only, and when it ends
impersonation.endedSessions are ended early from the console. It records how many

The API

RouteBodyAnswers
POST /api/users/:id/impersonate{ clientId, redirectUrl, minutes?, readOnly? }{ url, sessionExpiresAt, readOnly }. Open url
DELETE /api/users/:id/impersonatenone{ ended }, the number of sessions ended
RefusalWhen
403 console_onlyThe request came with an API token
403 owner_requiredreadOnly: false from someone without the owner role
400 selfYou tried to sign in as yourself
400 invalid_clientNo such client, or an agent client
400 invalid_redirect_urlredirectUrl is not one of the client's redirect URLs
409 user_blockedThe user is blocked

Next steps