# 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](/sign-in-setup/sign-in-as-a-user).

> **Note:**
  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:

| Field     | What it does                                                     |
| --------- | ---------------------------------------------------------------- |
| App       | The client to sign in to. An agent client is refused             |
| Open at   | One of that client's redirect URLs                               |
| Minutes   | How long the session lasts: 5 to 60, and 15 unless you change it |
| Read-only | On 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](/authentication/reverification) 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:

```tsx
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

| Row                     | Written when                                                                       |
| ----------------------- | ---------------------------------------------------------------------------------- |
| `impersonation.started` | A session starts. It records the client, whether it is read-only, and when it ends |
| `impersonation.ended`   | Sessions are ended early from the console. It records how many                     |

## The API

| Route                               | Body                                             | Answers                                           |
| ----------------------------------- | ------------------------------------------------ | ------------------------------------------------- |
| `POST /api/users/:id/impersonate`   | `{ clientId, redirectUrl, minutes?, readOnly? }` | `{ url, sessionExpiresAt, readOnly }`. Open `url` |
| `DELETE /api/users/:id/impersonate` | none                                             | `{ ended }`, the number of sessions ended         |

| Refusal                      | When                                                   |
| ---------------------------- | ------------------------------------------------------ |
| `403` `console_only`         | The request came with an API token                     |
| `403` `owner_required`       | `readOnly: false` from someone without the owner role  |
| `400` `self`                 | You tried to sign in as yourself                       |
| `400` `invalid_client`       | No such client, or an agent client                     |
| `400` `invalid_redirect_url` | `redirectUrl` is not one of the client's redirect URLs |
| `409` `user_blocked`         | The user is blocked                                    |

## Next steps

- [Custom claims](/authentication/custom-claims) - the other per-user setting on the same page.
- [Deleting accounts](/authentication/account-deletion) - delete a user from the console.
- [Sessions & Tokens](/authentication/sessions-and-tokens) - how a session is stored and ended.
