# Confirm it's you

Ask for a recent sign-in before a sensitive change, and let people re-confirm on the hosted page without losing their session.

Some changes should not ride on a session someone opened three weeks ago. Deleting the account, removing a connected account or a phone number, turning off text message codes: each needs a sign-in from the last 10 minutes. An older session is refused with `403 reauth_required`, and the person confirms it is still them on your hosted sign-in page. They come back to the page they were on, in the same session, now recent. Nobody is signed out, and your backend has no code to exchange.

Step by step, with screenshots: [The account security screens](/sign-in-setup/account-security-screens).

```tsx
import {
  isReauthRequired,
  useSaaSAuth,
  type WorkspaceApi,
} from '@buildbase/sdk/react';

function DeleteAccountButton({ api }: { api: WorkspaceApi }) {
  const { requireReverification } = useSaaSAuth();

  async function deleteAccount() {
    try {
      await api.requestAccountDeletion();
    } catch (error) {
      if (!isReauthRequired(error)) throw error;
      // Leaves for the sign-in page and comes back here. Ask again then.
      await requireReverification({ factors: error.factors });
    }
  }

  return <button onClick={deleteAccount}>Delete my account</button>;
}
```

The SDK's Security screen does this for you. Every section that can be refused offers **Confirm it's you**, and says once on the way back that the change can go ahead.

## What needs a recent sign-in

| Change                                 | Route                                 |
| -------------------------------------- | ------------------------------------- |
| Deleting the account                   | `POST /api/v1/public/account/delete`  |
| Removing a connected account or wallet | `DELETE /api/v1/public/accounts/:id`  |
| Removing the phone number              | `DELETE /api/v1/public/profile/phone` |
| Turning off text message codes         | `POST /api/v1/public/mfa/sms/disable` |

Recent means the session proved who its person is in the last 10 minutes, at the sign-in or at a later re-confirmation. A guest deleting their account is the one exception: a guest has no way to sign in again, so the session is all there is to ask about.

The refusal is a `403`, never a `401`, so the SDK does not take it for a dead session. It can carry `maxAgeSeconds`, the window in seconds, and `factors: ['mfa']` when the change also wants the second step.

## requireReverification

`useSaaSAuth().requireReverification(options?)` resolves `'recent'` or `'redirecting'`. `'redirecting'` means the browser is on its way to the hosted page: do nothing more in that call. The person comes back with `reverified=1` on the URL, which the provider takes off without a reload.

| Option          | Default                   | What it does                                                                                                                                            |
| --------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `returnUrl`     | The page the person is on | Where to come back to. See the rule below                                                                                                               |
| `factors`       | none                      | `['mfa']` also asks for the second step, from someone who has one                                                                                       |
| `maxAgeSeconds` | none                      | Resolve `'recent'` without leaving when the session proved who its person is within this many seconds. Without it, the person is always sent to confirm |

**The return URL** has to be on the origin of one of your client's redirect URLs. When the server refuses one with `invalid_return_url`, the SDK comes back through your `auth.redirectUrl` instead and then continues to the page, as it does after a sign-in.

A change of your own can ask first:

```tsx
const { requireReverification } = useSaaSAuth();

async function rotateApiKey() {
  // Signed in or re-confirmed in the last five minutes, or confirm first.
  if ((await requireReverification({ maxAgeSeconds: 300 })) === 'recent') {
    await createNewKey();
  }
}
```

That check runs in the browser, so treat it as the friendly half. If the change matters, your backend should refuse a stale session as well, from the `authTime` [described below](#reading-it-yourself).

`maxAgeSeconds` with `factors: ['mfa']` resolves `'recent'` only when the sign-in included a second step, so someone without one is sent to confirm every time. Check `user.mfaEnabled` before asking for it.

## On the hosted page

The sign-in page opens as **Confirm it's you**, with the person's address filled in. They sign in with any method your client offers, and the page sends them back.

- **The second step.** With `factors: ['mfa']`, a person who has a [second step](/authentication/two-step) is asked for it, even on a device they trusted to skip it.
- **Someone else.** Signing in as a different account is refused with `wrong_account`, and the session keeps its old sign-in time.
- **An ended session.** If the session was signed out while they were away, they get `session_ended` and sign in again from scratch.

## Sessions that cannot re-confirm

Some sessions have nobody behind them who could sign in again. `requireReverification` throws `reverify_unavailable` for them:

- a session made from an API key, which never counts as recent
- a session an app owner opened from the console, see [Signing in as a user](/authentication/sign-in-as)
- a guest's session, see [Guest users](/authentication/guests)
- a session for an agent client

The Security screen falls back to signing out and in again for these. It never does that to a guest, who would lose the account.

## Reading it yourself

`useSaaSAuth().session` carries how and when the session last proved who its person is:

| Field      | What it holds                                                                                        |
| ---------- | ---------------------------------------------------------------------------------------------------- |
| `authTime` | ISO 8601: the sign-in, or the latest re-confirmation. Absent for a session from the API-key exchange |
| `amr`      | How, as RFC 8176 values such as `['pwd', 'mfa']`                                                     |

Your backend reads the same two from `GET /api/v1/public/profile`, with the session in the `x-session-id` header. There `authTime` is `null` for a session nobody signed in to, so a route of yours can hold itself to the same 10 minutes.

## The API

`POST /api/v1/public/session/reverify` with `{ "returnUrl": "https://app.example.com/settings", "factors": ["mfa"] }` answers `{ url }`: the hosted page, bound to this session. `factors` is optional. Send the browser to `url`.

| Refusal                      | When                                                                                                |
| ---------------------------- | --------------------------------------------------------------------------------------------------- |
| `400` `invalid_return_url`   | `returnUrl` is not on the origin of one of the client's redirect URLs                               |
| `400` `reverify_unavailable` | Nobody can re-confirm this session: one from an API key, an owner's, a guest's or an agent client's |

## Next steps

- [Deleting accounts](/authentication/account-deletion) - the change that asks for a recent sign-in most often.
- [Connected Accounts](/authentication/connected-accounts) - removing a sign-in method, and the last way in.
- [Sessions & Tokens](/authentication/sessions-and-tokens) - how long a session lasts, and where it is stored.
