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

# User and Session Management

Before this: create and initialize a Dynamic client (see [Creating a Dynamic Client](https://docs.dynamic.xyz/docs/javascript/reference/client/create-dynamic-client.md), [Initializing the Dynamic Client](https://docs.dynamic.xyz/docs/javascript/reference/client/initialize-dynamic-client.md)).

Here, **user** means the person using your app; **Dynamic user** is the object created after they authenticate. A user can be signed in (e.g. wallet connected) without yet having a Dynamic user. See [Signing in with a Wallet](https://docs.dynamic.xyz/docs/javascript/authentication-methods/external-wallets.md) for more.

## Check if signed in

What counts as "signed in" is your app's business logic, not ours. Wrap the check in a helper you own so the rule lives in one place. The minimal version is "an authenticated user exists, or any wallet is connected":

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

// Your helper. Extend it with whatever your app treats as "signed in".
const isSignedIn = () =>
  Boolean(dynamicClient.user) || isAnyWalletAccountConnected();

console.log(isSignedIn());
```

Most apps treat "signed in" as more than the minimal check. Add the conditions your app requires to the same helper:

* **Authenticated user**: `dynamicClient.user` is set once the user authenticates.
* **Wallet connected**: `isAnyWalletAccountConnected()` (or `getWalletAccounts()`) covers verified and unverified wallets.
* **Dynamic requirements satisfied**: `checkUserScopes({ user, scopes: ['user:basic'] })` confirms the user has passed all Dynamic-controlled gates (KYC fields, MFA, recovery codes, device registration). See [Check User Scopes](https://docs.dynamic.xyz/docs/javascript/authentication-methods/is-user-onboarding-complete.md).
* **Device registered**: `isDeviceRegistrationRequired(user)`. See [Check if the user needs to complete device registration](#check-if-the-user-needs-to-complete-device-registration).
* **MFA satisfied**: `isUserMissingMfaAuth()` and `isPendingRecoveryCodesAcknowledgment()` flag MFA the user still owes.
* **Required embedded wallet**: `getChainsMissingWaasWalletAccounts()` from `@dynamic-labs-sdk/client/waas` returns the chains where the user still needs an embedded wallet.

You're also encouraged to fold in your own app-specific logic (a completed server-side session, accepted terms of service, a required plan or role) so "signed in" means exactly what your app needs, all in one helper:

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

const isSignedIn = () => {
  const user = dynamicClient.user;
  if (!user) return false;

  return (
    checkUserScopes({ user, scopes: ['user:basic'] }) &&
    getChainsMissingWaasWalletAccounts().length === 0 &&
    myAppHasActiveSession() // your own logic
  );
};
```

## Log the user out

Call the `logout` function, and it will clear all the session data, including the Dynamic user and any connected wallets (non verified wallets).

> [tab] JavaScript
>
> ```javascript theme={"system"} theme={"system"}
> import { logout } from '@dynamic-labs-sdk/client';
>
> const handleLogout = async () => {
>   await logout();
> };
>
> // ...
> ```

> [tab] React
>
> ```tsx theme={"system"}
> import { useLogout } from '@dynamic-labs-sdk/react-hooks';
>
> function LogoutButton() {
>   const { mutate: logout } = useLogout();
>   return <button onClick={() => logout()}>Log out</button>;
> }
> ```

## Get the current authenticated user

> [tab] JavaScript
>
> ```javascript theme={"system"} theme={"system"}
> // If the user is authenticated, the user object will be available in the dynamicClient.user property
> const user = dynamicClient.user;
> console.log(user);
> ```

> [tab] React
>
> Use `useUser()` so your component re-renders when the user logs in or out:
>
> ```tsx theme={"system"}
> import { useUser } from '@dynamic-labs-sdk/react-hooks';
>
> function UserProfile() {
>   const { data: user } = useUser(); // re-renders on userChanged
>
>   if (!user) return <p>Not logged in</p>;
>   return <p>Welcome, {user.email}</p>;
> }
> ```

## Get the user JWT

> [tab] JavaScript
>
> ```javascript theme={"system"} theme={"system"}
> // If the user is authenticated and you are not using cookies authentication,
> // the jwt will be available in the dynamicClient.token property
> const jwt = dynamicClient.token;
> console.log(jwt);
> ```

> [tab] React
>
> Subscribe to `tokenChanged` so your component updates when the JWT changes:
>
> ```tsx theme={"system"}
> import { dynamicClient } from './dynamicClient';
> import { useOnEvent } from '@dynamic-labs-sdk/react-hooks';
> import { useState } from 'react';
>
> function useAuthToken() {
>   const [token, setToken] = useState(dynamicClient.token);
>
>   useOnEvent({
>     event: 'tokenChanged',
>     listener: () => setToken(dynamicClient.token),
>   });
>
>   return token;
> }
> ```

## Update the current authenticated user

Call `updateUser` to update the current Dynamic user.
You can pass many user fields to the function, and it will update the user with the new values.
If any of the fields require OTP verification (like email or phone number), the function will return an `OTPVerification` object,
that you can use to check what kind of OTP verification is required (email or phone number), send the OTP to the user, and verify the OTP.

> [tab] JavaScript
>
> ```javascript theme={"system"} theme={"system"}
> import {
>   sendEmailOTP,
>   updateUser,
>   verifyOTP
> } from '@dynamic-labs-sdk/client';
>
> const handleUpdateUser = async (newName, newEmail) => {
>   const otpVerification = await updateUser({
>     userFields: { firstName: newName, email: newEmail },
>   });
>   console.log(otpVerification);
>
>   if (!otpVerification) {
>     return;
>   }
>
>   if (otpVerification?.email) {
>     // This call will send the OTP to the user's email, you should then collect the OTP from the user and then call the verifyOTP function
>     await sendEmailOTP({ email: newEmail });
>   }
> };
>
> const handleVerifyOTP = async (otpVerification, otpCode) => {
>   // Use the same otpVerification object that you got from the updateUser function
>   await verifyOTP({ otpVerification, verificationToken: otpCode });
> };
>
> // ...
> ```

> [tab] React
>
> ```tsx theme={"system"}
> import { useSendEmailOTP, useUpdateUser, useVerifyOTP } from '@dynamic-labs-sdk/react-hooks';
> import { useState } from 'react';
>
> function EditProfileForm() {
>   const [name, setName] = useState('');
>   const [email, setEmail] = useState('');
>   const [otp, setOtp] = useState('');
>
>   const { mutate: updateUser, data: otpVerification, reset: resetUpdate } = useUpdateUser();
>   const { mutate: sendEmailOTP } = useSendEmailOTP();
>   const { mutate: verifyOTP } = useVerifyOTP();
>
>   const handleSave = () => {
>     updateUser({ userFields: { firstName: name, email } }, {
>       onSuccess: (verification) => {
>         if (verification?.email) {
>           sendEmailOTP({ email });
>         }
>       },
>     });
>   };
>
>   if (otpVerification?.email) {
>     return (
>       <div>
>         <input value={otp} onChange={(e) => setOtp(e.target.value)} placeholder="Enter OTP" />
>         <button onClick={() => verifyOTP(
>           { otpVerification, verificationToken: otp },
>           { onSuccess: () => resetUpdate() }
>         )}>Verify</button>
>       </div>
>     );
>   }
>
>   return (
>     <div>
>       <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Name" />
>       <input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" />
>       <button onClick={handleSave}>Save</button>
>     </div>
>   );
> }
> ```

## Check if a user has missing fields

You can toggle user data collected and also to be required or not in the [Dynamic Console](https://console.dynamic.xyz/dashboard/log-in-user-profile).
We don't control that in the JavaScript SDK, but you can check if a user has missing fields by calling the `user.missingFields` property.
With that, you can display the appropriate UI to the user to complete the onboarding process, and call the `updateUser` function to update the user with the missing fields.

```javascript theme={"system"} theme={"system"}

const onUserAuthenticated = async () => {
  const { missingFields } = dynamicClient.user;

  if (!missingFields.length) {
    // User has no missing fields
    return;
  }

  missingFields.forEach((field) => {
    console.log(`Name : ${field.name}`);
    // If you just want to know the required fields, you can check the field.required property
    console.log(`Required : ${field.required}`);
    console.log(`Type : ${field.type}`);
    // You can check if OTP verification is required for this field by checking the field.verification property
    console.log(`Type : ${field.verification}`);
  });
};

// ...

```

## Check if Dynamic requirements are satisfied

Use `checkUserScopes` to verify the user has the `user:basic` scope, meaning all Dynamic-controlled requirements (KYC fields, MFA, recovery codes, device registration) are met:

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

const onUserAuthenticated = () => {
  const user = dynamicClient.user;
  if (!user) return;

  if (!checkUserScopes({ user, scopes: ['user:basic'] })) {
    // User has pending Dynamic requirements
    redirectToOnboarding();
  } else {
    redirectToDashboard();
  }
};
```

See [checkUserScopes](https://docs.dynamic.xyz/docs/javascript/authentication-methods/is-user-onboarding-complete.md) for the full reference and guidance on composing your own readiness checks.

## Check if the user needs to complete device registration

After authentication, you can check whether the current device needs to be registered and complete registration on redirect. For the full flow, see [Device Registration](https://docs.dynamic.xyz/docs/javascript/authentication-methods/device-registration.md).

```javascript theme={"system"} theme={"system"}
import {
  completeDeviceRegistration,
  detectDeviceRegistrationRedirect,
  getDeviceRegistrationTokenFromUrl,
  isDeviceRegistrationRequired,
} from '@dynamic-labs-sdk/client';

const user = dynamicClient.user;

if (user && isDeviceRegistrationRequired(user)) {
  // Show UI telling the user to verify their device from the email link
}

const completeRegistrationIfNeeded = async () => {
  const url = window.location.href;

  if (detectDeviceRegistrationRedirect({ url })) {
    const deviceToken = getDeviceRegistrationTokenFromUrl({ url });
    await completeDeviceRegistration({ deviceToken });
  }
};


```

## Handle temporarily unavailable session storage

On the web, the SDK keeps the user's session signing key and device signing key in the browser's IndexedDB. Browsers close IndexedDB when a device is short on memory, most often on mobile Safari, so a call that needs one of those keys can fail while the key itself is intact. The SDK reconnects and retries the read for up to 3 seconds. When storage is still unreachable after that, it throws `KeychainUnavailableError`.

Tell the two failures apart:

* `KeychainUnavailableError`: storage is unreachable right now. The key is not lost, so ask the user to try again in a moment.
* `KeyNotFoundError`: the key is gone. Retrying cannot bring it back, so log the user out and let them sign in again.

Offer the user both paths: retry, and sign in again if they would rather not wait. For the causes behind this and what the SDK does before it gives up, see [IndexedDB is unavailable or closing](https://docs.dynamic.xyz/docs/get-started/troubleshooting/browser-storage/indexeddb-unavailable.md).

> [tab] JavaScript
>
> ```javascript theme={"system"} theme={"system"}
> import {
>   KeychainUnavailableError,
>   KeyNotFoundError,
>   logout,
> } from '@dynamic-labs-sdk/client';
>
> // showRetryPrompt and showSignInPrompt are your implementation.
> const withSessionKeyRecovery = async (action) => {
>   try {
>     return await action();
>   } catch (error) {
>     if (error instanceof KeychainUnavailableError) {
>       showRetryPrompt({
>         onRetry: () => withSessionKeyRecovery(action),
>         onSignInAgain: async () => {
>           await logout();
>           showSignInPrompt();
>         },
>       });
>       return;
>     }
>
>     if (error instanceof KeyNotFoundError) {
>       await logout();
>       showSignInPrompt();
>       return;
>     }
>
>     throw error;
>   }
> };
> ```

> [tab] React
>
> ```tsx theme={"system"}
> import { KeychainUnavailableError, KeyNotFoundError } from '@dynamic-labs-sdk/client';
> import { useLogout } from '@dynamic-labs-sdk/react-hooks';
> import { useState } from 'react';
>
> // Wrap every call that needs a signing key in run. While isStorageBusy is true,
> // render a retry button that calls run again, and a log out button beside it.
> const useSessionKeyRecovery = () => {
>   const [isStorageBusy, setIsStorageBusy] = useState(false);
>   const { mutate: logout } = useLogout();
>
>   const run = async (action: () => Promise<void>) => {
>     try {
>       await action();
>       setIsStorageBusy(false);
>     } catch (error) {
>       if (error instanceof KeychainUnavailableError) {
>         setIsStorageBusy(true);
>         return;
>       }
>
>       if (error instanceof KeyNotFoundError) {
>         setIsStorageBusy(false);
>         logout();
>         return;
>       }
>
>       throw error;
>     }
>   };
>
>   return { isStorageBusy, run };
> };
> ```

## Refreshing the current authenticated user (Dynamic user)

You can refresh the current authenticated user (Dynamic user) by calling the `refreshUser` function.
This function will refresh the user object in the SDK with the latest data from the server.

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

const handleRefreshUser = async () => {
  await refreshUser();
};

// ...

```


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