Headless step-up authentication using the JavaScript SDK — check, verify, and the SDK handles the rest.
The JavaScript SDK gives you full control over step-up authentication without any UI framework. Works with vanilla JS, React, Vue, Svelte, Angular, or any other setup.For concepts, scopes, and token lifecycle, see Step-up authentication overview.
The pattern is always: check → verify → proceed. After verification, the elevated access token is automatically stored and applied to subsequent Dynamic API calls. You never need to manually handle the token.
import { checkStepUpAuth, authenticatePasskeyMFA, TokenScope } from '@dynamic-labs-sdk/client';const exportPrivateKey = async () => { // 1. Check if step-up is required and get available credentials const { isRequired, credentials } = await checkStepUpAuth({ scope: TokenScope.Walletexport, }); if (isRequired) { // 2. Verify — the token is stored and applied automatically await authenticatePasskeyMFA({ requestedScopes: [TokenScope.Walletexport], }); } // 3. Proceed — the SDK attaches the token to the relevant API call await performExport();};
checkStepUpAuth is the recommended way to determine whether step-up is required. It performs a fast local check first (if a valid elevated token already exists, it returns immediately without an API call), then falls back to a server-authoritative check that evaluates your environment configuration, SDK version, MFA settings, and action-based MFA requirements. On failure, it defaults to { isRequired: true } for safety.
import { checkStepUpAuth, TokenScope } from '@dynamic-labs-sdk/client';const { isRequired, credentials, defaultCredentialId } = await checkStepUpAuth({ scope: TokenScope.Walletexport });if (isRequired) { // credentials contains the user's available verification methods // Each credential has: { id, format, type?, alias? } // defaultCredentialId is the recommended credential to authenticate with: // - For MFA users: the configured default device or most recently added // - For re-auth users: the credential used to sign in const defaultCred = credentials.find((c) => c.id === defaultCredentialId); console.log('Recommended credential:', defaultCred);}
The response type:
import { JwtVerifiedCredentialFormatEnum } from '@dynamic-labs-sdk/client';type StepUpCheckResponse = { isRequired: boolean; credentials: StepUpCredential[]; /** The recommended credential to authenticate with. * For MFA: the user's default device or most recently added. * For re-auth: the credential used to sign in. */ defaultCredentialId?: string;};type StepUpCredential = { id: string; format: JwtVerifiedCredentialFormatEnum; type?: string; alias?: string;};
checkStepUpAuth (shown above) is the recommended approach. It combines the token check with credential discovery in a single call. Use getElevatedAccessToken only if you need a simple synchronous local-only check.
Use getElevatedAccessToken to check if the user already has a non-expired elevated token for a scope. Pass consume: false to check without removing a single-use token:
import { getElevatedAccessToken, TokenScope } from '@dynamic-labs-sdk/client';if (getElevatedAccessToken({ scope: TokenScope.Walletexport, consume: false })) { // Token exists and is valid — proceed with the operation} else { // No valid token — trigger verification}
Pass requestedScopes to any verification method. After success, the elevated token is automatically stored and applied to Dynamic API calls.
Email OTP
SMS OTP
Wallet Signature (external wallets only)
Social (OAuth)
Passkey MFA
TOTP MFA
Recovery Code
import { sendEmailOTP, verifyOTP, TokenScope } from '@dynamic-labs-sdk/client';// 1. Send the OTP to the user's verified emailconst otpVerification = await sendEmailOTP({ email });// 2. Verify with the code the user enteredawait verifyOTP({ otpVerification, verificationToken: otpCode, requestedScopes: [TokenScope.Credentiallink],});// Token is now stored and will be applied automatically
Social re-authentication uses the same OAuth redirect flow as social sign-in. Pass requestedScopes to completeSocialRedirect when the user returns.
import { signInWithSocialRedirect, completeSocialRedirect, ProviderEnum, TokenScope,} from '@dynamic-labs-sdk/client';// 1. Redirect the user to the social providerawait signInWithSocialRedirect({ provider: ProviderEnum.Google, redirectUrl: window.location.href,});// 2. After redirect, complete the flow and request the elevated tokenconst url = new URL(window.location.href);await completeSocialRedirect({ url, requestedScopes: [TokenScope.Credentiallink],});
import { authenticatePasskeyMFA, TokenScope } from '@dynamic-labs-sdk/client';// Browser prompts for passkey (biometric, PIN, or security key)await authenticatePasskeyMFA({ requestedScopes: [TokenScope.Walletexport],});
The JavaScript SDK requires you to choose which verification method to present. checkStepUpAuth returns all available credentials — both MFA (passkey, TOTP) and re-auth (email, SMS, OAuth, wallet) — so you can route the user to the right method in a single call:
import { checkStepUpAuth, JwtVerifiedCredentialFormatEnum, TokenScope,} from '@dynamic-labs-sdk/client';const chooseMethod = async () => { const { isRequired, credentials, defaultCredentialId } = await checkStepUpAuth({ scope: TokenScope.Walletexport }); if (!isRequired) return null; // No step-up needed // Use the default credential if available, otherwise pick the first const credential = credentials.find((c) => c.id === defaultCredentialId) ?? credentials[0]; switch (credential?.format) { case JwtVerifiedCredentialFormatEnum.Passkey: return 'passkey'; // Use authenticatePasskeyMFA case JwtVerifiedCredentialFormatEnum.Totp: return 'totp'; // Use authenticateTotpMfaDevice (collect code first) case JwtVerifiedCredentialFormatEnum.Email: return 'email'; // Use sendEmailOTP + verifyOTP case JwtVerifiedCredentialFormatEnum.PhoneNumber: return 'phone'; // Use sendSmsOTP + verifyOTP case JwtVerifiedCredentialFormatEnum.Oauth: return 'oauth'; // Use signInWithSocialRedirect + completeSocialRedirect case JwtVerifiedCredentialFormatEnum.Blockchain: return 'wallet'; // Use verifyWalletAccount default: return credential?.format; }};
When a user has MFA methods registered, the backend requires MFA for elevated token issuance — re-auth credentials won’t be returned. checkStepUpAuth handles this automatically, so you don’t need separate logic for MFA vs. re-auth users.
In React, drive each authentication step with its hook — useCheckStepUpAuth (or call checkStepUpAuth inside a handler), useAuthenticatePasskeyMFA, useAuthenticateTotpMfaDevice, useSendEmailOTP, and useVerifyOTP — and use useState to track which verification UI to show. The flow mirrors the full example below.