> ## Documentation Index
> Fetch the complete documentation index at: https://docs.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 [Business Accounts](https://docs.dynamic.xyz/docs/business-accounts/accounts.md) for the model. For what an elevated access token is and when the backend requires one, see [Step-Up Authentication](https://docs.dynamic.xyz/docs/business-accounts/step-up-auth.md).

Most business account modifications 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` |

All scopes above are always enforced except `business_account:link_wallet`, which is required by default but can be turned off in [Console > Security](https://console.dynamic.xyz/dashboard/security) under Business Account Actions.

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, TokenScope } from '@dynamic-labs-sdk/client';

const result = await checkStepUpAuth({ scope: TokenScope.BusinessAccountsigneradd });

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

## How to obtain an elevated token

> [!NOTE]
>
> React Native has no popup, so use a verification the user completes in the app. See [React Native Business Accounts](https://docs.dynamic.xyz/docs/javascript/react-native/business-accounts.md).

### 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 { authenticatePasskeyMFA, TokenScope } from '@dynamic-labs-sdk/client';

await authenticatePasskeyMFA({
  requestedScopes: [TokenScope.BusinessAccountsigneradd],
});
```

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, authenticatePasskeyMFA, TokenScope } from '@dynamic-labs-sdk/client';
import { addBusinessAccountSigner } from '@dynamic-labs-sdk/client/waas';

const scope = TokenScope.BusinessAccountsigneradd;

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

// 2. If so, obtain the elevated token
if (isRequired) {
  await authenticatePasskeyMFA({ 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/core';
import { addBusinessAccountSigner } from '@dynamic-labs-sdk/client/waas';

try {
  await addBusinessAccountSigner({
    businessAccountId,
    walletAccount,
    targetIdentity: { identifier: 'teammate@acme.com', identifierType: 'email' },
  });
} catch (error) {
  if (error instanceof APIError && error.status === 403) {
    // Step-up token missing or expired — re-verify
  }
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.