> ## 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.

# External Auth Step-Up

> Request elevated access tokens using your own backend's signed JWTs — no user interaction required.

If you use [Bring Your Own Auth (BYOA)](/overview/authentication/bring-your-own-auth), you can issue elevated access tokens directly from your backend without prompting the user for re-authentication or MFA. Your backend signs an assertion JWT and the SDK exchanges it for an elevated access token.

This is useful when your backend has already verified the user's identity through its own means (e.g., a session, an internal auth check, or a custom challenge) and you want to authorize a sensitive Dynamic operation without additional user friction.

For concepts, scopes, and token lifecycle, see [Step-up authentication overview](/overview/authentication/step-up-auth).

## Prerequisites

* `@dynamic-labs-sdk/client` initialized
* [External auth (BYOA)](/overview/authentication/bring-your-own-auth) configured with a JWKS URL in your dashboard
* The user is already signed in via external auth

## How it works

1. Your backend creates an **assertion JWT** signed with the same key registered in your environment's JWKS URL.
2. The SDK sends this JWT to Dynamic's backend, which validates the signature and issues an elevated access token.
3. The elevated token is automatically stored in SDK state and attached to subsequent API calls — no manual token handling is needed.

```mermaid theme={"system"}
sequenceDiagram
    participant App
    participant YourBackend as Your Backend
    participant SDK
    participant Dynamic as Dynamic Backend

    App->>YourBackend: Request elevated access
    note right of YourBackend: Verify user identity,<br/>sign assertion JWT
    YourBackend-->>App: Assertion JWT

    App->>SDK: requestExternalAuthElevatedToken({ externalJwt })
    SDK->>Dynamic: POST /externalAuth/verify (assertion JWT)
    Dynamic-->>SDK: Elevated access token
    SDK->>SDK: Store token in client state

    App->>SDK: Perform sensitive operation
    note right of SDK: Token attached automatically
    SDK->>Dynamic: Operation + elevated token
    Dynamic-->>App: Success
```

## Assertion JWT requirements

Your backend must sign a JWT with the following claims:

| Claim   | Required | Description                                                 |
| ------- | -------- | ----------------------------------------------------------- |
| `sub`   | Yes      | The user's external user ID (must match the signed-in user) |
| `scope` | Yes      | Space-delimited scopes to request (e.g., `"wallet:export"`) |
| `jti`   | Yes      | A unique token identifier (prevents replay attacks)         |
| `exp`   | Yes      | Expiration time (Unix timestamp in seconds)                 |

Example payload:

```json theme={"system"}
{
  "sub": "user-123",
  "scope": "wallet:export",
  "jti": "unique-request-id-abc",
  "exp": 1711900000
}
```

The JWT must be signed using the same algorithm and key that corresponds to your environment's JWKS URL.

## Usage

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

// 1. Get the assertion JWT from your backend
const { jwt } = await fetch('/api/step-up', {
  method: 'POST',
  body: JSON.stringify({ action: 'wallet:export' }),
}).then((res) => res.json());

// 2. Exchange it for an elevated access token
await requestExternalAuthElevatedToken({ externalJwt: jwt });

// 3. Proceed — the SDK attaches the token to the relevant API call
await performExport();
```

## Combining with `checkStepUpAuth`

You can use `checkStepUpAuth` to determine whether step-up is required before calling your backend:

```javascript theme={"system"}
import {
  checkStepUpAuth,
  requestExternalAuthElevatedToken,
} from '@dynamic-labs-sdk/client';
import { TokenScope } from '@dynamic-labs/sdk-api-core';

const exportPrivateKey = async () => {
  const { isRequired } = await checkStepUpAuth({
    scope: TokenScope.Walletexport,
  });

  if (isRequired) {
    // Get assertion JWT from your backend
    const { jwt } = await fetchAssertionJwt('wallet:export');

    // Exchange for elevated access token
    await requestExternalAuthElevatedToken({ externalJwt: jwt });
  }

  // Proceed with the operation
  await performExport();
};
```

## When to use external auth vs. other methods

| Approach          | Best for                                                                                                 |
| ----------------- | -------------------------------------------------------------------------------------------------------- |
| **External auth** | BYOA setups where your backend controls identity verification. No user interaction needed on the client. |
| **Re-auth / MFA** | When you want Dynamic to handle user verification (OTP, passkey, TOTP, etc.)                             |

Both approaches produce the same elevated access token and are stored and consumed identically by the SDK.

## React

Use `useRequestExternalAuthElevatedToken` for the token exchange and call the one-shot `checkStepUpAuth` check directly inside the button handler:

```tsx theme={"system"}
import { checkStepUpAuth } from '@dynamic-labs-sdk/client';
import { TokenScope } from '@dynamic-labs/sdk-api-core';
import { useRequestExternalAuthElevatedToken } from '@dynamic-labs-sdk/react-hooks';
import { useState } from 'react';

function ExportKeyButton() {
  const [status, setStatus] = useState('');
  const { mutate: requestExternalAuthElevatedToken } = useRequestExternalAuthElevatedToken();

  const handleExport = async () => {
    setStatus('Checking...');
    const { isRequired } = await checkStepUpAuth({ scope: TokenScope.Walletexport });

    if (isRequired) {
      // Get assertion JWT from your backend
      const { jwt } = await fetch('/api/step-up', {
        method: 'POST',
        body: JSON.stringify({ action: 'wallet:export' }),
      }).then((res) => res.json());

      requestExternalAuthElevatedToken(
        { externalJwt: jwt },
        { onSuccess: () => setStatus('Proceeding with export...') }
      );
      return;
    }

    setStatus('Proceeding with export...');
  };

  return (
    <div>
      <button onClick={handleExport}>Export Private Key</button>
      {status && <p>{status}</p>}
    </div>
  );
}
```

## Related

* [Step-Up Authentication Overview](/overview/authentication/step-up-auth) — Concepts, scopes, token lifecycle
* [Bring Your Own Auth](/overview/authentication/bring-your-own-auth) — Configuring external auth
* [JavaScript SDK Step-Up Guide](/javascript/authentication-methods/step-up-auth/overview) — Re-auth and MFA methods
