> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Step-Up Authentication

> Obtain the elevated token required before calling sensitive business account operations.

<Note>
  Business Accounts are in **early access**. See the [overview](/docs/javascript/reference/business-accounts/overview) for the model.
</Note>

Most business account mutations require an **elevated access token** — a short-lived JWT scoped to a specific action. The regular session token is not enough. If the call proceeds without one, the backend returns a `403`.

## Which operations require step-up

| Operation                          | Required scope                        |
| ---------------------------------- | ------------------------------------- |
| `addBusinessAccountSigner`         | `business_account:signer:add`         |
| `removeBusinessAccountSigner`      | `business_account:signer:remove`      |
| `addBusinessAccountMember`         | `business_account:member:add`         |
| `removeBusinessAccountMember`      | `business_account:member:remove`      |
| `updateBusinessAccountMemberRole`  | `business_account:member:role:update` |
| `transferBusinessAccountOwnership` | `business_account:transfer_ownership` |
| `addWalletToBusinessAccount`       | `business_account:link_wallet`        |
| `removeBusinessAccountWallet`      | `business_account:wallet:remove`      |

These do **not** require step-up: `createBusinessAccount`, `getBusinessAccount`, `listBusinessAccounts`, `createWalletForBusinessAccount`.

## Check whether step-up is needed

`checkStepUpAuth` does a fast local check first — if a valid elevated token for the scope is already in state, it returns `{ isRequired: false }` without a network round-trip. Only if a fresh token is needed does it call the backend.

```js theme={"system"}
import { checkStepUpAuth } from '@dynamic-labs-sdk/client';

const result = await checkStepUpAuth({ scope: 'business_account:signer:add' });

if (result.isRequired) {
  // Prompt the user to verify (see options below)
}
```

## How to obtain an elevated token

### Option 1 — MFA / passkey verify with `requestedScopes`

Pass `requestedScopes` when verifying with MFA, a passkey, or email OTP. The SDK parses the elevated token from the verify response and stores it automatically.

```js theme={"system"}
import { serverAuthenticatePasskey } from '@dynamic-labs-sdk/client';

await serverAuthenticatePasskey({
  requestedScopes: ['business_account:signer:add'],
});
```

The same `requestedScopes` parameter is available on `authenticateTotpMfaDevice`, `verifyOTP`, and `signInWithSocialPopUp`.

### Option 2 — External auth assertion (server-side)

If your backend issues its own JWTs (registered via your environment's JWKS URL), mint a short-lived JWT containing `sub`, `scope`, `jti`, and `exp` claims and pass it to `requestExternalAuthElevatedToken`.

```js theme={"system"}
import { requestExternalAuthElevatedToken } from '@dynamic-labs-sdk/client';

// externalJwt is signed by your backend and contains the scope claim
await requestExternalAuthElevatedToken({ externalJwt });
```

### Option 3 — Step-up challenge flow

Use the `credentials` and `defaultCredentialId` from `checkStepUpAuth` to drive your own challenge UI, then pass the completed credential to a verify call with `requestedScopes`.

## Putting it together

```js theme={"system"}
import { checkStepUpAuth, serverAuthenticatePasskey, addBusinessAccountSigner } from '@dynamic-labs-sdk/client';

const scope = 'business_account:signer:add';

// 1. Check if step-up is needed
const { isRequired } = await checkStepUpAuth({ scope });

// 2. If so, obtain the elevated token
if (isRequired) {
  await serverAuthenticatePasskey({ requestedScopes: [scope] });
}

// 3. Proceed — the SDK attaches the elevated token automatically
await addBusinessAccountSigner({
  businessAccountId,
  walletAccount,
  targetIdentity: { identifier: 'teammate@acme.com', identifierType: 'email' },
});
```

The SDK retrieves the stored elevated token and attaches it as `x-dyn-elevated-access-token` on the request. You do not pass the token manually.

## Error handling

If the elevated token is missing or expired, the backend returns a `403`. Catch it and prompt for re-verification:

```js theme={"system"}
import { APIError } from '@dynamic-labs-sdk/client';

try {
  await addBusinessAccountSigner({ ... });
} catch (error) {
  if (error instanceof APIError && error.status === 403) {
    // Step-up token missing or expired — re-verify
  }
}
```
