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.
If you use Bring Your Own Auth (BYOA), you can issue elevated access tokens directly from your backend without prompting the user for re-authentication or MFA. The useExternalAuth hook provides verifyWithExternalJwt to exchange your backend’s assertion JWT 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.
Prerequisites
DynamicContextProvider configured with your environment ID
- External auth (BYOA) configured with a JWKS URL in your dashboard
- The user is already signed in via external auth
How it works
- Your backend creates an assertion JWT signed with the same key registered in your environment’s JWKS URL.
- The
verifyWithExternalJwt method sends this JWT to Dynamic’s backend, which validates the signature and issues an elevated access token.
- The elevated token is automatically stored in SDK state and attached to subsequent API calls — no manual token handling is needed.
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:
{
"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.
Quick start
import { useExternalAuth } from '@dynamic-labs/sdk-react-core';
const ExportButton = () => {
const { verifyWithExternalJwt } = useExternalAuth();
const handleExport = async () => {
// 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 verifyWithExternalJwt({ externalJwt: jwt });
// 3. Proceed — the SDK attaches the token automatically
await exportWallet();
};
return <button onClick={handleExport}>Export Wallet</button>;
};
Combining with isStepUpRequired
Use isStepUpRequired to check whether step-up is needed before calling your backend:
import { useExternalAuth } from '@dynamic-labs/sdk-react-core';
import { useStepUpAuthentication } from '@dynamic-labs/sdk-react-core';
import { TokenScope } from '@dynamic-labs/sdk-api-core';
const ExportButton = () => {
const { verifyWithExternalJwt } = useExternalAuth();
const { isStepUpRequired } = useStepUpAuthentication();
const handleExport = async () => {
if (await isStepUpRequired({ scope: TokenScope.Walletexport })) {
// Get assertion JWT from your backend
const { jwt } = await fetchAssertionJwt('wallet:export');
// Exchange for elevated access token
await verifyWithExternalJwt({ externalJwt: jwt });
}
// Proceed with the operation
await exportWallet();
};
return <button onClick={handleExport}>Export Wallet</button>;
};
Full example: credential unlinking with error handling
import { FC, useState } from 'react';
import { useExternalAuth } from '@dynamic-labs/sdk-react-core';
import { useStepUpAuthentication } from '@dynamic-labs/sdk-react-core';
import { TokenScope } from '@dynamic-labs/sdk-api-core';
const UnlinkCredentialButton: FC<{ credentialId: string }> = ({
credentialId,
}) => {
const { verifyWithExternalJwt } = useExternalAuth();
const { isStepUpRequired } = useStepUpAuthentication();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleUnlink = async () => {
setIsLoading(true);
setError(null);
try {
if (await isStepUpRequired({ scope: TokenScope.Credentialunlink })) {
const { jwt } = await fetch('/api/step-up', {
method: 'POST',
body: JSON.stringify({ action: 'credential:unlink' }),
}).then((res) => res.json());
await verifyWithExternalJwt({ externalJwt: jwt });
}
await unlinkCredential(credentialId);
} catch (e) {
setError(e instanceof Error ? e.message : 'Step-up verification failed');
} finally {
setIsLoading(false);
}
};
return (
<div>
<button onClick={handleUnlink} disabled={isLoading}>
{isLoading ? 'Verifying...' : 'Unlink Credential'}
</button>
{error && <p>{error}</p>}
</div>
);
};
When to use external auth vs. other methods
| Approach | Best for |
|---|
External auth (verifyWithExternalJwt) | BYOA setups where your backend controls identity verification. No user interaction needed on the client. |
promptStepUpAuth | When you want Dynamic to handle user verification with its built-in UI. |
| Individual verify methods | Headless custom UI where Dynamic handles the verification logic. |
Both approaches produce the same elevated access token and are stored and consumed identically by the SDK.
useExternalAuth reference
const { signInWithExternalJwt, verifyWithExternalJwt } = useExternalAuth();
| Method | Description |
|---|
signInWithExternalJwt({ externalUserId, externalJwt }) | Signs in a user with an external JWT. Returns UserProfile. |
verifyWithExternalJwt({ externalJwt }) | Exchanges an assertion JWT for an elevated access token. The token is automatically stored and applied. |