diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/network/model/GetTokenResponseJson.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/network/model/GetTokenResponseJson.kt
index 5b612ed85..369782c41 100644
--- a/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/network/model/GetTokenResponseJson.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/network/model/GetTokenResponseJson.kt
@@ -44,10 +44,10 @@ sealed class GetTokenResponseJson {
         val expiresInSeconds: Int,
 
         @SerialName("Key")
-        val key: String,
+        val key: String?,
 
         @SerialName("PrivateKey")
-        val privateKey: String,
+        val privateKey: String?,
 
         @SerialName("Kdf")
         val kdfType: KdfTypeJson,
diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepository.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepository.kt
index d9f199778..0026f6acf 100644
--- a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepository.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepository.kt
@@ -68,6 +68,11 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
      */
     val yubiKeyResultFlow: Flow<YubiKeyResult>
 
+    /**
+     * The organization identifier currently associated with this user.
+     */
+    var organizationIdentifier: String?
+
     /**
      * The two-factor response data necessary for login and also to populate the
      * Two-Factor Login screen.
@@ -153,12 +158,14 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
     /**
      * Attempt to login using a SSO flow. Updated access token will be reflected in [authStateFlow].
      */
+    @Suppress("LongParameterList")
     suspend fun login(
         email: String,
         ssoCode: String,
         ssoCodeVerifier: String,
         ssoRedirectUri: String,
         captchaToken: String?,
+        organizationIdentifier: String,
     ): LoginResult
 
     /**
@@ -210,11 +217,11 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
     ): ResetPasswordResult
 
     /**
-     * Sets the user's password to [password] for the user within the given [organizationId] with
-     * an optional [passwordHint].
+     * Sets the user's password to [password] for the user within the given [organizationIdentifier]
+     * with an optional [passwordHint].
      */
     suspend fun setPassword(
-        organizationId: String,
+        organizationIdentifier: String,
         password: String,
         passwordHint: String?,
     ): SetPasswordResult
diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt
index 391893027..88b71b4c4 100644
--- a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt
@@ -59,6 +59,7 @@ import com.x8bit.bitwarden.data.auth.repository.util.policyInformation
 import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
 import com.x8bit.bitwarden.data.auth.repository.util.toUserState
 import com.x8bit.bitwarden.data.auth.repository.util.toUserStateJson
+import com.x8bit.bitwarden.data.auth.repository.util.toUserStateJsonWithPassword
 import com.x8bit.bitwarden.data.auth.repository.util.userOrganizationsList
 import com.x8bit.bitwarden.data.auth.repository.util.userOrganizationsListFlow
 import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_PBKDF2_ITERATIONS
@@ -157,6 +158,8 @@ class AuthRepositoryImpl(
 
     private val ioScope = CoroutineScope(dispatcherManager.io)
 
+    override var organizationIdentifier: String? = null
+
     override var twoFactorResponse: TwoFactorRequired? = null
 
     override val activeUserId: String? get() = authDiskSource.userState?.activeUserId
@@ -400,6 +403,7 @@ class AuthRepositoryImpl(
         ssoCodeVerifier: String,
         ssoRedirectUri: String,
         captchaToken: String?,
+        organizationIdentifier: String,
     ): LoginResult = loginCommon(
         email = email,
         authModel = IdentityTokenAuthModel.SingleSignOn(
@@ -408,19 +412,21 @@ class AuthRepositoryImpl(
             ssoRedirectUri = ssoRedirectUri,
         ),
         captchaToken = captchaToken,
+        orgIdentifier = organizationIdentifier,
     )
 
     /**
      * A helper function to extract the common logic of logging in through
      * any of the available methods.
      */
-    @Suppress("LongMethod")
+    @Suppress("CyclomaticComplexMethod", "LongMethod")
     private suspend fun loginCommon(
         email: String,
         password: String? = null,
         authModel: IdentityTokenAuthModel,
         twoFactorData: TwoFactorDataModel? = null,
         deviceData: DeviceDataModel? = null,
+        orgIdentifier: String? = null,
         captchaToken: String?,
     ): LoginResult = identityService
         .getToken(
@@ -474,6 +480,11 @@ class AuthRepositoryImpl(
                             )
                         }
 
+                        // Set the current organization identifier for use in JIT provisioning.
+                        if (loginResponse.userDecryptionOptions?.hasMasterPassword == false) {
+                            organizationIdentifier = orgIdentifier
+                        }
+
                         // Remove any cached data after successfully logging in.
                         identityTokenAuthModel = null
                         twoFactorResponse = null
@@ -482,17 +493,19 @@ class AuthRepositoryImpl(
 
                         // Attempt to unlock the vault with password if possible.
                         password?.let {
-                            vaultRepository.unlockVault(
-                                userId = userStateJson.activeUserId,
-                                email = userStateJson.activeAccount.profile.email,
-                                kdf = userStateJson.activeAccount.profile.toSdkParams(),
-                                userKey = loginResponse.key,
-                                privateKey = loginResponse.privateKey,
-                                masterPassword = it,
-                                // We can separately unlock the vault for organization data after
-                                // receiving the sync response if this data is currently absent.
-                                organizationKeys = null,
-                            )
+                            if (loginResponse.privateKey != null && loginResponse.key != null) {
+                                vaultRepository.unlockVault(
+                                    userId = userStateJson.activeUserId,
+                                    email = userStateJson.activeAccount.profile.email,
+                                    kdf = userStateJson.activeAccount.profile.toSdkParams(),
+                                    userKey = loginResponse.key,
+                                    privateKey = loginResponse.privateKey,
+                                    masterPassword = it,
+                                    // We can separately unlock vault for organization data after
+                                    // receiving the sync response if this data is currently absent.
+                                    organizationKeys = null,
+                                )
+                            }
 
                             // Save the master password hash.
                             authSdkSource
@@ -515,34 +528,37 @@ class AuthRepositoryImpl(
                         }
 
                         // Attempt to unlock the vault with auth request if possible.
-                        deviceData?.let { model ->
-                            vaultRepository.unlockVault(
-                                userId = userStateJson.activeUserId,
-                                email = userStateJson.activeAccount.profile.email,
-                                kdf = userStateJson.activeAccount.profile.toSdkParams(),
-                                privateKey = loginResponse.privateKey,
-                                initUserCryptoMethod = InitUserCryptoMethod.AuthRequest(
-                                    requestPrivateKey = model.privateKey,
-                                    method = model
-                                        .masterPasswordHash
-                                        ?.let {
-                                            AuthRequestMethod.MasterKey(
-                                                protectedMasterKey = model.asymmetricalKey,
-                                                authRequestKey = loginResponse.key,
-                                            )
-                                        }
-                                        ?: AuthRequestMethod.UserKey(
-                                            protectedUserKey = model.asymmetricalKey,
-                                        ),
-                                ),
-                                // We can separately unlock the vault for organization data after
-                                // receiving the sync response if this data is currently absent.
-                                organizationKeys = null,
-                            )
-                            // We are purposely not storing the master password hash here since it
-                            // is not formatted in in a manner that we can use. We will store it
-                            // properly the next time the user enters their master password and
-                            // it is validated.
+                        // These values will only be null during the Just-in-Time provisioning flow.
+                        if (loginResponse.privateKey != null && loginResponse.key != null) {
+                            deviceData?.let { model ->
+                                vaultRepository.unlockVault(
+                                    userId = userStateJson.activeUserId,
+                                    email = userStateJson.activeAccount.profile.email,
+                                    kdf = userStateJson.activeAccount.profile.toSdkParams(),
+                                    privateKey = loginResponse.privateKey,
+                                    initUserCryptoMethod = InitUserCryptoMethod.AuthRequest(
+                                        requestPrivateKey = model.privateKey,
+                                        method = model
+                                            .masterPasswordHash
+                                            ?.let {
+                                                AuthRequestMethod.MasterKey(
+                                                    protectedMasterKey = model.asymmetricalKey,
+                                                    authRequestKey = loginResponse.key,
+                                                )
+                                            }
+                                            ?: AuthRequestMethod.UserKey(
+                                                protectedUserKey = model.asymmetricalKey,
+                                            ),
+                                    ),
+                                    // We can separately unlock  vault for organization data after
+                                    // receiving the sync response if this data is currently absent.
+                                    organizationKeys = null,
+                                )
+                                // We are purposely not storing the master password hash here since
+                                // it is not formatted in in a manner that we can use. We will store
+                                // it properly the next time the user enters their master password
+                                // and it is validated.
+                            }
                         }
 
                         authDiskSource.storeAccountTokens(
@@ -805,8 +821,9 @@ class AuthRepositoryImpl(
             )
     }
 
+    @Suppress("LongMethod")
     override suspend fun setPassword(
-        organizationId: String,
+        organizationIdentifier: String,
         password: String,
         passwordHint: String?,
     ): SetPasswordResult {
@@ -832,28 +849,40 @@ class AuthRepositoryImpl(
                 kdf = activeAccount.profile.toSdkParams(),
             )
             .flatMap { keyResponse ->
-                accountsService.setPassword(
-                    body = SetPasswordRequestJson(
-                        passwordHash = passwordHash,
-                        passwordHint = passwordHint,
-                        organizationIdentifier = organizationId,
-                        kdfIterations = activeAccount.profile.kdfIterations,
-                        kdfMemory = activeAccount.profile.kdfMemory,
-                        kdfParallelism = activeAccount.profile.kdfParallelism,
-                        kdfType = activeAccount.profile.kdfType,
-                        key = keyResponse.encryptedUserKey,
-                        keys = RegisterRequestJson.Keys(
-                            publicKey = keyResponse.keys.public,
-                            encryptedPrivateKey = keyResponse.keys.private,
+                accountsService
+                    .setPassword(
+                        body = SetPasswordRequestJson(
+                            passwordHash = passwordHash,
+                            passwordHint = passwordHint,
+                            organizationIdentifier = organizationIdentifier,
+                            kdfIterations = activeAccount.profile.kdfIterations,
+                            kdfMemory = activeAccount.profile.kdfMemory,
+                            kdfParallelism = activeAccount.profile.kdfParallelism,
+                            kdfType = activeAccount.profile.kdfType,
+                            key = keyResponse.encryptedUserKey,
+                            keys = RegisterRequestJson.Keys(
+                                publicKey = keyResponse.keys.public,
+                                encryptedPrivateKey = keyResponse.keys.private,
+                            ),
                         ),
-                    ),
-                )
+                    )
+                    .onSuccess {
+                        authDiskSource.storePrivateKey(
+                            userId = activeAccount.profile.userId,
+                            privateKey = keyResponse.keys.private,
+                        )
+                        authDiskSource.storeUserKey(
+                            userId = activeAccount.profile.userId,
+                            userKey = keyResponse.encryptedUserKey,
+                        )
+                    }
             }
             .onSuccess {
                 authDiskSource.storeMasterPasswordHash(
                     userId = activeAccount.profile.userId,
                     passwordHash = passwordHash,
                 )
+                authDiskSource.userState = authDiskSource.userState?.toUserStateJsonWithPassword()
             }
             .fold(
                 onFailure = { SetPasswordResult.Error },
diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/util/UserStateJsonExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/util/UserStateJsonExtensions.kt
index 6abb5defc..d932c811c 100644
--- a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/util/UserStateJsonExtensions.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/util/UserStateJsonExtensions.kt
@@ -1,6 +1,7 @@
 package com.x8bit.bitwarden.data.auth.repository.util
 
 import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
+import com.x8bit.bitwarden.data.auth.datasource.network.model.UserDecryptionOptionsJson
 import com.x8bit.bitwarden.data.auth.repository.model.UserOrganizations
 import com.x8bit.bitwarden.data.auth.repository.model.UserState
 import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
@@ -40,6 +41,35 @@ fun UserStateJson.toUpdatedUserStateJson(
         )
 }
 
+/**
+ * Updates the [UserStateJson] to set the `hasMasterPassword` value to `true` after a user sets
+ * their password.
+ */
+fun UserStateJson.toUserStateJsonWithPassword(): UserStateJson {
+    val account = this.accounts[activeUserId] ?: return this
+    val profile = account.profile
+    val updatedProfile = profile
+        .copy(
+            userDecryptionOptions = profile
+                .userDecryptionOptions
+                ?.copy(hasMasterPassword = true)
+                ?: UserDecryptionOptionsJson(
+                    hasMasterPassword = true,
+                    keyConnectorUserDecryptionOptions = null,
+                    trustedDeviceUserDecryptionOptions = null,
+                ),
+        )
+    val updatedAccount = account.copy(profile = updatedProfile)
+    return this
+        .copy(
+            accounts = accounts
+                .toMutableMap()
+                .apply {
+                    replace(activeUserId, updatedAccount)
+                },
+        )
+}
+
 /**
  * Converts the given [UserStateJson] to a [UserState] using the given [vaultState].
  */
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/auth/AuthNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/auth/AuthNavigation.kt
index bb993c5c1..e42fd41f0 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/auth/AuthNavigation.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/auth/AuthNavigation.kt
@@ -20,6 +20,8 @@ import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.loginWithDeviceDestin
 import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.navigateToLoginWithDevice
 import com.x8bit.bitwarden.ui.auth.feature.masterpasswordhint.masterPasswordHintDestination
 import com.x8bit.bitwarden.ui.auth.feature.masterpasswordhint.navigateToMasterPasswordHint
+import com.x8bit.bitwarden.ui.auth.feature.setpassword.navigateToSetPassword
+import com.x8bit.bitwarden.ui.auth.feature.setpassword.setPasswordDestination
 import com.x8bit.bitwarden.ui.auth.feature.trusteddevice.trustedDeviceDestination
 import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.navigateToTwoFactorLogin
 import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.twoFactorLoginDestination
@@ -49,6 +51,7 @@ fun NavGraphBuilder.authGraph(navController: NavHostController) {
         )
         enterpriseSignOnDestination(
             onNavigateBack = { navController.popBackStack() },
+            onNavigateToSetPassword = { navController.navigateToSetPassword() },
             onNavigateToTwoFactorLogin = { emailAddress ->
                 navController.navigateToTwoFactorLogin(
                     emailAddress = emailAddress,
@@ -56,6 +59,7 @@ fun NavGraphBuilder.authGraph(navController: NavHostController) {
                 )
             },
         )
+        setPasswordDestination()
         landingDestination(
             onNavigateToCreateAccount = { navController.navigateToCreateAccount() },
             onNavigateToLogin = { emailAddress ->
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnNavigation.kt
index f6cf381fc..006ba80a1 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnNavigation.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnNavigation.kt
@@ -38,6 +38,7 @@ fun NavController.navigateToEnterpriseSignOn(
  */
 fun NavGraphBuilder.enterpriseSignOnDestination(
     onNavigateBack: () -> Unit,
+    onNavigateToSetPassword: () -> Unit,
     onNavigateToTwoFactorLogin: (emailAddress: String) -> Unit,
 ) {
     composableWithSlideTransitions(
@@ -48,6 +49,7 @@ fun NavGraphBuilder.enterpriseSignOnDestination(
     ) {
         EnterpriseSignOnScreen(
             onNavigateBack = onNavigateBack,
+            onNavigateToSetPassword = onNavigateToSetPassword,
             onNavigateToTwoFactorLogin = onNavigateToTwoFactorLogin,
         )
     }
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnScreen.kt
index aeeaf013f..d42f6d66c 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnScreen.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnScreen.kt
@@ -50,6 +50,7 @@ import com.x8bit.bitwarden.ui.platform.theme.LocalIntentManager
 @Composable
 fun EnterpriseSignOnScreen(
     onNavigateBack: () -> Unit,
+    onNavigateToSetPassword: () -> Unit,
     onNavigateToTwoFactorLogin: (String) -> Unit,
     intentManager: IntentManager = LocalIntentManager.current,
     viewModel: EnterpriseSignOnViewModel = hiltViewModel(),
@@ -67,6 +68,10 @@ fun EnterpriseSignOnScreen(
                 intentManager.startCustomTabsActivity(event.uri)
             }
 
+            is EnterpriseSignOnEvent.NavigateToSetPassword -> {
+                onNavigateToSetPassword()
+            }
+
             is EnterpriseSignOnEvent.NavigateToTwoFactorLogin -> {
                 onNavigateToTwoFactorLogin(event.emailAddress)
             }
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnViewModel.kt
index d81287b84..a1cfd331d 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnViewModel.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnViewModel.kt
@@ -343,7 +343,8 @@ class EnterpriseSignOnViewModel @Inject constructor(
                                 ssoCode = ssoCallbackResult.code,
                                 ssoCodeVerifier = ssoData.codeVerifier,
                                 ssoRedirectUri = SSO_URI,
-                                captchaToken = mutableStateFlow.value.captchaToken,
+                                captchaToken = state.captchaToken,
+                                organizationIdentifier = state.orgIdentifierInput,
                             )
                         sendAction(EnterpriseSignOnAction.Internal.OnLoginResult(result))
                     }
@@ -472,6 +473,11 @@ sealed class EnterpriseSignOnEvent {
      */
     data class NavigateToCaptcha(val uri: Uri) : EnterpriseSignOnEvent()
 
+    /**
+     * Navigates to the set master password screen.
+     */
+    data object NavigateToSetPassword : EnterpriseSignOnEvent()
+
     /**
      * Navigates to the two-factor login screen.
      */
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordNavigation.kt
new file mode 100644
index 000000000..5b1a40481
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordNavigation.kt
@@ -0,0 +1,28 @@
+package com.x8bit.bitwarden.ui.auth.feature.setpassword
+
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptions
+import androidx.navigation.compose.composable
+
+const val SET_PASSWORD_ROUTE: String = "set_password"
+
+/**
+ * Add the Set Password screen to the nav graph.
+ */
+fun NavGraphBuilder.setPasswordDestination() {
+    composable(
+        route = SET_PASSWORD_ROUTE,
+    ) {
+        SetPasswordScreen()
+    }
+}
+
+/**
+ * Navigate to the Set Password screen.
+ */
+fun NavController.navigateToSetPassword(
+    navOptions: NavOptions? = null,
+) {
+    this.navigate(SET_PASSWORD_ROUTE, navOptions)
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordScreen.kt
new file mode 100644
index 000000000..49f25a417
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordScreen.kt
@@ -0,0 +1,206 @@
+package com.x8bit.bitwarden.ui.auth.feature.setpassword
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.imePadding
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.rememberTopAppBarState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.testTag
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.x8bit.bitwarden.R
+import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenMediumTopAppBar
+import com.x8bit.bitwarden.ui.platform.components.button.BitwardenTextButton
+import com.x8bit.bitwarden.ui.platform.components.dialog.BasicDialogState
+import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
+import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
+import com.x8bit.bitwarden.ui.platform.components.dialog.LoadingDialogState
+import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField
+import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
+import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
+import com.x8bit.bitwarden.ui.platform.components.text.BitwardenPolicyWarningText
+
+/**
+ * The top level composable for the Set Master Password screen.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SetPasswordScreen(
+    viewModel: SetPasswordViewModel = hiltViewModel(),
+) {
+    val state by viewModel.stateFlow.collectAsStateWithLifecycle()
+    SetPasswordDialogs(
+        dialogState = state.dialogState,
+        onDismissRequest = remember(viewModel) {
+            { viewModel.trySendAction(SetPasswordAction.DialogDismiss) }
+        },
+    )
+
+    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
+    BitwardenScaffold(
+        modifier = Modifier
+            .fillMaxSize()
+            .nestedScroll(scrollBehavior.nestedScrollConnection),
+        topBar = {
+            BitwardenMediumTopAppBar(
+                title = stringResource(id = R.string.set_master_password),
+                scrollBehavior = scrollBehavior,
+                actions = {
+                    BitwardenTextButton(
+                        label = stringResource(id = R.string.cancel),
+                        onClick = remember(viewModel) {
+                            { viewModel.trySendAction(SetPasswordAction.CancelClick) }
+                        },
+                        modifier = Modifier.semantics { testTag = "CancelButton" },
+                    )
+                    BitwardenTextButton(
+                        label = stringResource(id = R.string.submit),
+                        onClick = remember(viewModel) {
+                            { viewModel.trySendAction(SetPasswordAction.SubmitClick) }
+                        },
+                        modifier = Modifier.semantics { testTag = "SubmitButton" },
+                    )
+                },
+            )
+        },
+    ) { innerPadding ->
+        SetPasswordScreenContent(
+            state = state,
+            onPasswordInputChanged = remember(viewModel) {
+                { viewModel.trySendAction(SetPasswordAction.PasswordInputChanged(it)) }
+            },
+            onRetypePasswordInputChanged = remember(viewModel) {
+                { viewModel.trySendAction(SetPasswordAction.RetypePasswordInputChanged(it)) }
+            },
+            onPasswordHintInputChanged = remember(viewModel) {
+                { viewModel.trySendAction(SetPasswordAction.PasswordHintInputChanged(it)) }
+            },
+            modifier = Modifier
+                .padding(innerPadding)
+                .imePadding()
+                .fillMaxSize(),
+        )
+    }
+}
+
+@Composable
+@Suppress("LongMethod")
+private fun SetPasswordScreenContent(
+    state: SetPasswordState,
+    onPasswordInputChanged: (String) -> Unit,
+    onRetypePasswordInputChanged: (String) -> Unit,
+    onPasswordHintInputChanged: (String) -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    Column(
+        modifier = modifier
+            .verticalScroll(rememberScrollState()),
+    ) {
+        Text(
+            text = stringResource(
+                id = R.string.your_organization_requires_you_to_set_a_master_password,
+            ),
+            style = MaterialTheme.typography.bodyMedium,
+            color = MaterialTheme.colorScheme.onSurfaceVariant,
+            modifier = Modifier
+                .padding(horizontal = 16.dp)
+                .fillMaxWidth(),
+        )
+
+        Spacer(modifier = Modifier.height(16.dp))
+
+        BitwardenPolicyWarningText(
+            text = stringResource(id = R.string.reset_password_auto_enroll_invite_warning),
+            style = MaterialTheme.typography.bodyMedium,
+            modifier = Modifier
+                .padding(horizontal = 16.dp)
+                .fillMaxWidth(),
+        )
+
+        Spacer(modifier = Modifier.height(16.dp))
+
+        BitwardenPasswordField(
+            label = stringResource(id = R.string.master_password),
+            value = state.passwordInput,
+            onValueChange = onPasswordInputChanged,
+            hint = stringResource(id = R.string.master_password_description),
+            modifier = Modifier
+                .semantics { testTag = "NewPasswordField" }
+                .padding(horizontal = 16.dp)
+                .fillMaxWidth(),
+        )
+
+        Spacer(modifier = Modifier.height(16.dp))
+
+        BitwardenPasswordField(
+            label = stringResource(id = R.string.retype_master_password),
+            value = state.retypePasswordInput,
+            onValueChange = onRetypePasswordInputChanged,
+            modifier = Modifier
+                .semantics { testTag = "RetypePasswordField" }
+                .padding(horizontal = 16.dp)
+                .fillMaxWidth(),
+        )
+
+        Spacer(modifier = Modifier.height(16.dp))
+
+        BitwardenTextField(
+            label = stringResource(id = R.string.master_password_hint),
+            value = state.passwordHintInput,
+            onValueChange = onPasswordHintInputChanged,
+            hint = stringResource(id = R.string.master_password_hint_description),
+            modifier = Modifier
+                .semantics { testTag = "MasterPasswordHintLabel" }
+                .fillMaxWidth()
+                .padding(horizontal = 16.dp),
+        )
+
+        Spacer(modifier = Modifier.navigationBarsPadding())
+    }
+}
+
+@Composable
+private fun SetPasswordDialogs(
+    dialogState: SetPasswordState.DialogState?,
+    onDismissRequest: () -> Unit,
+) {
+    when (dialogState) {
+        is SetPasswordState.DialogState.Error -> {
+            BitwardenBasicDialog(
+                visibilityState = BasicDialogState.Shown(
+                    title = dialogState.title,
+                    message = dialogState.message,
+                ),
+                onDismissRequest = onDismissRequest,
+            )
+        }
+
+        is SetPasswordState.DialogState.Loading -> {
+            BitwardenLoadingDialog(
+                visibilityState = LoadingDialogState.Shown(
+                    text = dialogState.message,
+                ),
+            )
+        }
+
+        null -> Unit
+    }
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordViewModel.kt
new file mode 100644
index 000000000..e11eca412
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordViewModel.kt
@@ -0,0 +1,385 @@
+package com.x8bit.bitwarden.ui.auth.feature.setpassword
+
+import android.os.Parcelable
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.viewModelScope
+import com.x8bit.bitwarden.R
+import com.x8bit.bitwarden.data.auth.repository.AuthRepository
+import com.x8bit.bitwarden.data.auth.repository.model.SetPasswordResult
+import com.x8bit.bitwarden.data.vault.repository.VaultRepository
+import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
+import com.x8bit.bitwarden.ui.auth.feature.resetpassword.util.toDisplayLabels
+import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
+import com.x8bit.bitwarden.ui.platform.base.util.Text
+import com.x8bit.bitwarden.ui.platform.base.util.asText
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import kotlinx.parcelize.Parcelize
+import javax.inject.Inject
+
+private const val KEY_STATE = "state"
+private const val MIN_PASSWORD_LENGTH = 12
+
+/**
+ * Manages application state for the Set Password screen.
+ */
+@HiltViewModel
+@Suppress("TooManyFunctions")
+class SetPasswordViewModel @Inject constructor(
+    private val authRepository: AuthRepository,
+    private val vaultRepository: VaultRepository,
+    savedStateHandle: SavedStateHandle,
+) : BaseViewModel<SetPasswordState, SetPasswordEvent, SetPasswordAction>(
+    initialState = savedStateHandle[KEY_STATE] ?: run {
+        val organizationIdentifier = authRepository.organizationIdentifier
+        if (organizationIdentifier.isNullOrBlank()) authRepository.logout()
+        SetPasswordState(
+            dialogState = null,
+            organizationIdentifier = organizationIdentifier.orEmpty(),
+            passwordInput = "",
+            passwordHintInput = "",
+            policies = authRepository.passwordPolicies.toDisplayLabels(),
+            retypePasswordInput = "",
+        )
+    },
+) {
+    override fun handleAction(action: SetPasswordAction) {
+        when (action) {
+            SetPasswordAction.CancelClick -> handleCancelClick()
+            SetPasswordAction.SubmitClick -> handleSubmitClicked()
+            SetPasswordAction.DialogDismiss -> handleDialogDismiss()
+
+            is SetPasswordAction.PasswordInputChanged -> handlePasswordInputChanged(action)
+
+            is SetPasswordAction.RetypePasswordInputChanged -> {
+                handleRetypePasswordInputChanged(action)
+            }
+
+            is SetPasswordAction.PasswordHintInputChanged -> {
+                handlePasswordHintInputChanged(action)
+            }
+
+            is SetPasswordAction.Internal.ReceiveUnlockVaultResult -> {
+                handleReceiveUnlockVaultResult(action)
+            }
+
+            is SetPasswordAction.Internal.ReceiveSetPasswordResult -> {
+                handleReceiveSetPasswordResult(action)
+            }
+
+            is SetPasswordAction.Internal.ReceiveValidatePasswordAgainstPoliciesResult -> {
+                handleReceiveValidatePasswordAgainstPoliciesResult(action)
+            }
+        }
+    }
+
+    /**
+     * Dismiss the view if the user cancels the set master password functionality.
+     */
+    private fun handleCancelClick() {
+        authRepository.logout()
+    }
+
+    /**
+     * Validate the user's current password when they submit.
+     */
+    private fun handleSubmitClicked() {
+        // Display an error dialog if the new password field is blank.
+        if (state.passwordInput.isBlank()) {
+            mutableStateFlow.update {
+                it.copy(
+                    dialogState = SetPasswordState.DialogState.Error(
+                        title = R.string.an_error_has_occurred.asText(),
+                        message = R.string.validation_field_required
+                            .asText(R.string.master_password.asText()),
+                    ),
+                )
+            }
+            return
+        }
+
+        // Validate password against policies if there are any.
+        if (state.policies.isNotEmpty()) {
+            viewModelScope.launch {
+                sendAction(
+                    SetPasswordAction.Internal.ReceiveValidatePasswordAgainstPoliciesResult(
+                        authRepository.validatePasswordAgainstPolicies(state.passwordInput),
+                    ),
+                )
+            }
+        } else if (state.passwordInput.length < MIN_PASSWORD_LENGTH) {
+            mutableStateFlow.update {
+                it.copy(
+                    dialogState = SetPasswordState.DialogState.Error(
+                        title = R.string.an_error_has_occurred.asText(),
+                        message = R.string.master_password_length_val_message_x
+                            .asText(MIN_PASSWORD_LENGTH),
+                    ),
+                )
+            }
+        } else if (state.passwordInput == state.retypePasswordInput) {
+            setPassword()
+        } else {
+            mutableStateFlow.update {
+                it.copy(
+                    dialogState = SetPasswordState.DialogState.Error(
+                        title = R.string.an_error_has_occurred.asText(),
+                        message = R.string.master_password_confirmation_val_message.asText(),
+                    ),
+                )
+            }
+        }
+    }
+
+    /**
+     * Dismiss the dialog state.
+     */
+    private fun handleDialogDismiss() {
+        mutableStateFlow.update {
+            it.copy(
+                dialogState = null,
+            )
+        }
+    }
+
+    /**
+     * Update the state with the new master password input.
+     */
+    private fun handlePasswordInputChanged(action: SetPasswordAction.PasswordInputChanged) {
+        mutableStateFlow.update {
+            it.copy(
+                passwordInput = action.input,
+            )
+        }
+    }
+
+    /**
+     * Update the state with the re-typed master password input.
+     */
+    private fun handleRetypePasswordInputChanged(
+        action: SetPasswordAction.RetypePasswordInputChanged,
+    ) {
+        mutableStateFlow.update {
+            it.copy(
+                retypePasswordInput = action.input,
+            )
+        }
+    }
+
+    /**
+     * Update the state with the password hint input.
+     */
+    private fun handlePasswordHintInputChanged(
+        action: SetPasswordAction.PasswordHintInputChanged,
+    ) {
+        mutableStateFlow.update {
+            it.copy(
+                passwordHintInput = action.input,
+            )
+        }
+    }
+
+    private fun handleReceiveUnlockVaultResult(
+        action: SetPasswordAction.Internal.ReceiveUnlockVaultResult,
+    ) {
+        when (action.result) {
+            is VaultUnlockResult.Success -> {
+                mutableStateFlow.update { it.copy(dialogState = null) }
+            }
+
+            is VaultUnlockResult.AuthenticationError,
+            is VaultUnlockResult.InvalidStateError,
+            is VaultUnlockResult.GenericError,
+            -> {
+                mutableStateFlow.update {
+                    it.copy(
+                        dialogState = SetPasswordState.DialogState.Error(
+                            title = R.string.an_error_has_occurred.asText(),
+                            message = R.string.generic_error_message.asText(),
+                        ),
+                    )
+                }
+            }
+        }
+    }
+
+    /**
+     * Show an alert if the set password attempt failed, otherwise attempt to unlock the vault.
+     */
+    private fun handleReceiveSetPasswordResult(
+        action: SetPasswordAction.Internal.ReceiveSetPasswordResult,
+    ) {
+        when (action.result) {
+            SetPasswordResult.Error -> {
+                mutableStateFlow.update {
+                    it.copy(
+                        dialogState = SetPasswordState.DialogState.Error(
+                            title = R.string.an_error_has_occurred.asText(),
+                            message = R.string.generic_error_message.asText(),
+                        ),
+                    )
+                }
+            }
+
+            SetPasswordResult.Success -> {
+                viewModelScope.launch {
+                    sendAction(
+                        SetPasswordAction.Internal.ReceiveUnlockVaultResult(
+                            result = vaultRepository.unlockVaultWithMasterPassword(
+                                masterPassword = state.passwordInput,
+                            ),
+                        ),
+                    )
+                }
+            }
+        }
+    }
+
+    /**
+     * Display an alert if the password doesn't meet the policy requirements, then check that
+     * the new password matches the retyped password and that the current password is valid.
+     */
+    private fun handleReceiveValidatePasswordAgainstPoliciesResult(
+        action: SetPasswordAction.Internal.ReceiveValidatePasswordAgainstPoliciesResult,
+    ) {
+        // Display an error alert if the new password doesn't meet the policy requirements.
+        if (!action.meetsRequirements) {
+            mutableStateFlow.update {
+                it.copy(
+                    dialogState = SetPasswordState.DialogState.Error(
+                        title = R.string.master_password_policy_validation_title.asText(),
+                        message = R.string.master_password_policy_validation_message.asText(),
+                    ),
+                )
+            }
+        }
+    }
+
+    /**
+     * A helper function to launch the set password request.
+     */
+    private fun setPassword() {
+        // Show the loading dialog.
+        mutableStateFlow.update {
+            it.copy(
+                dialogState = SetPasswordState.DialogState.Loading(
+                    message = R.string.updating_password.asText(),
+                ),
+            )
+        }
+        viewModelScope.launch {
+            sendAction(
+                SetPasswordAction.Internal.ReceiveSetPasswordResult(
+                    result = authRepository.setPassword(
+                        organizationIdentifier = state.organizationIdentifier,
+                        password = state.passwordInput,
+                        passwordHint = state.passwordHintInput,
+                    ),
+                ),
+            )
+        }
+    }
+}
+
+/**
+ * Models state of the Set Password screen.
+ */
+@Parcelize
+data class SetPasswordState(
+    val dialogState: DialogState?,
+    val organizationIdentifier: String,
+    val passwordHintInput: String,
+    val passwordInput: String,
+    val policies: List<Text>,
+    val retypePasswordInput: String,
+) : Parcelable {
+    /**
+     * Represents the current state of any dialogs on the screen.
+     */
+    sealed class DialogState : Parcelable {
+        /**
+         * Represents an error dialog with the given [message] and optional [title]. If no title
+         * is specified a default will be provided.
+         */
+        @Parcelize
+        data class Error(
+            val title: Text? = null,
+            val message: Text,
+        ) : DialogState()
+
+        /**
+         * Represents a loading dialog with the given [message].
+         */
+        @Parcelize
+        data class Loading(
+            val message: Text,
+        ) : DialogState()
+    }
+}
+
+/**
+ * Models events for the Set Password screen.
+ */
+sealed class SetPasswordEvent
+
+/**
+ * Models actions for the Set Password screen.
+ */
+sealed class SetPasswordAction {
+    /**
+     * Indicates that the user has confirmed logging out.
+     */
+    data object CancelClick : SetPasswordAction()
+
+    /**
+     * Indicates that the user has clicked the submit button.
+     */
+    data object SubmitClick : SetPasswordAction()
+
+    /**
+     * Indicates that the dialog has been dismissed.
+     */
+    data object DialogDismiss : SetPasswordAction()
+
+    /**
+     * Indicates that the master password input has changed.
+     */
+    data class PasswordInputChanged(val input: String) : SetPasswordAction()
+
+    /**
+     * Indicates that the re-type master password input has changed.
+     */
+    data class RetypePasswordInputChanged(val input: String) : SetPasswordAction()
+
+    /**
+     * Indicates that the password hint input has changed.
+     */
+    data class PasswordHintInputChanged(val input: String) : SetPasswordAction()
+
+    /**
+     * Models actions that the [SetPasswordViewModel] might send itself.
+     */
+    sealed class Internal : SetPasswordAction() {
+        /**
+         * Indicates that a login result has been received.
+         */
+        data class ReceiveUnlockVaultResult(
+            val result: VaultUnlockResult,
+        ) : Internal()
+
+        /**
+         * Indicates that a set password result has been received.
+         */
+        data class ReceiveSetPasswordResult(
+            val result: SetPasswordResult,
+        ) : Internal()
+
+        /**
+         * Indicates that a validate password against policies result has been received.
+         */
+        data class ReceiveValidatePasswordAgainstPoliciesResult(
+            val meetsRequirements: Boolean,
+        ) : Internal()
+    }
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreen.kt
index 5918fae05..c2a853bdc 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreen.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreen.kt
@@ -19,6 +19,8 @@ import com.x8bit.bitwarden.ui.auth.feature.auth.navigateToAuthGraph
 import com.x8bit.bitwarden.ui.auth.feature.resetpassword.RESET_PASSWORD_ROUTE
 import com.x8bit.bitwarden.ui.auth.feature.resetpassword.navigateToResetPasswordGraph
 import com.x8bit.bitwarden.ui.auth.feature.resetpassword.resetPasswordDestination
+import com.x8bit.bitwarden.ui.auth.feature.setpassword.SET_PASSWORD_ROUTE
+import com.x8bit.bitwarden.ui.auth.feature.setpassword.navigateToSetPassword
 import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.VAULT_UNLOCK_ROUTE
 import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.navigateToVaultUnlock
 import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.vaultUnlockDestination
@@ -87,6 +89,7 @@ fun RootNavScreen(
     val targetRoute = when (state) {
         RootNavState.Auth -> AUTH_GRAPH_ROUTE
         RootNavState.ResetPassword -> RESET_PASSWORD_ROUTE
+        is RootNavState.SetPassword -> SET_PASSWORD_ROUTE
         RootNavState.Splash -> SPLASH_ROUTE
         RootNavState.VaultLocked -> VAULT_UNLOCK_ROUTE
         is RootNavState.VaultUnlocked,
@@ -126,6 +129,7 @@ fun RootNavScreen(
         when (val currentState = state) {
             RootNavState.Auth -> navController.navigateToAuthGraph(rootNavOptions)
             RootNavState.ResetPassword -> navController.navigateToResetPasswordGraph(rootNavOptions)
+            is RootNavState.SetPassword -> navController.navigateToSetPassword(rootNavOptions)
             RootNavState.Splash -> navController.navigateToSplash(rootNavOptions)
             RootNavState.VaultLocked -> navController.navigateToVaultUnlock(rootNavOptions)
             is RootNavState.VaultUnlocked -> navController.navigateToVaultUnlockedGraph(
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModel.kt
index 42dd85274..97bbb606a 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModel.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModel.kt
@@ -60,6 +60,8 @@ class RootNavViewModel @Inject constructor(
         val userState = action.userState
         val specialCircumstance = action.specialCircumstance
         val updatedRootNavState = when {
+            userState?.activeAccount?.needsMasterPassword == true -> RootNavState.SetPassword
+
             userState?.activeAccount?.needsPasswordReset == true -> RootNavState.ResetPassword
 
             userState == null ||
@@ -117,6 +119,12 @@ sealed class RootNavState : Parcelable {
     @Parcelize
     data object ResetPassword : RootNavState()
 
+    /**
+     * App should show set password graph.
+     */
+    @Parcelize
+    data object SetPassword : RootNavState()
+
     /**
      * App should show splash nav graph.
      */
diff --git a/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/network/service/AccountsServiceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/network/service/AccountsServiceTest.kt
index 60864ddda..229852a1b 100644
--- a/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/network/service/AccountsServiceTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/network/service/AccountsServiceTest.kt
@@ -123,7 +123,7 @@ class AccountsServiceTest : BaseServiceTest() {
     fun `register success json should be Success`() = runTest {
         val json = """
             {
-              "captchaBypassToken": "mock_token"            
+              "captchaBypassToken": "mock_token"
             }
             """
         val expectedResponse = RegisterResponseJson.Success(
diff --git a/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt
index f25dcba63..c4c5cd61e 100644
--- a/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt
@@ -30,6 +30,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.ResetPasswordReque
 import com.x8bit.bitwarden.data.auth.datasource.network.model.SetPasswordRequestJson
 import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorAuthMethod
 import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
+import com.x8bit.bitwarden.data.auth.datasource.network.model.UserDecryptionOptionsJson
 import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
 import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesService
 import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedService
@@ -396,8 +397,8 @@ class AuthRepositoryTest {
                     userId = USER_ID_1,
                     email = EMAIL,
                     kdf = ACCOUNT_1.profile.toSdkParams(),
-                    userKey = successResponse.key,
-                    privateKey = successResponse.privateKey,
+                    userKey = successResponse.key!!,
+                    privateKey = successResponse.privateKey!!,
                     organizationKeys = null,
                     masterPassword = PASSWORD,
                 )
@@ -462,8 +463,8 @@ class AuthRepositoryTest {
                     userId = USER_ID_1,
                     email = EMAIL,
                     kdf = ACCOUNT_1.profile.toSdkParams(),
-                    userKey = successResponse.key,
-                    privateKey = successResponse.privateKey,
+                    userKey = successResponse.key!!,
+                    privateKey = successResponse.privateKey!!,
                     organizationKeys = null,
                     masterPassword = PASSWORD,
                 )
@@ -811,8 +812,8 @@ class AuthRepositoryTest {
                     userId = USER_ID_1,
                     email = EMAIL,
                     kdf = ACCOUNT_1.profile.toSdkParams(),
-                    userKey = successResponse.key,
-                    privateKey = successResponse.privateKey,
+                    userKey = successResponse.key!!,
+                    privateKey = successResponse.privateKey!!,
                     organizationKeys = null,
                     masterPassword = PASSWORD,
                 )
@@ -854,8 +855,8 @@ class AuthRepositoryTest {
                     userId = USER_ID_1,
                     email = EMAIL,
                     kdf = ACCOUNT_1.profile.toSdkParams(),
-                    userKey = successResponse.key,
-                    privateKey = successResponse.privateKey,
+                    userKey = successResponse.key!!,
+                    privateKey = successResponse.privateKey!!,
                     organizationKeys = null,
                     masterPassword = PASSWORD,
                 )
@@ -868,6 +869,82 @@ class AuthRepositoryTest {
             verify { settingsRepository.setDefaultsIfNecessary(userId = USER_ID_1) }
         }
 
+    @Test
+    @Suppress("MaxLineLength")
+    fun `login get token succeeds with null keys and hasMasterPassword false should not call unlockVault`() =
+        runTest {
+            val successResponse = GET_TOKEN_RESPONSE_SUCCESS.copy(
+                key = null,
+                privateKey = null,
+                userDecryptionOptions = UserDecryptionOptionsJson(
+                    hasMasterPassword = false,
+                    keyConnectorUserDecryptionOptions = null,
+                    trustedDeviceUserDecryptionOptions = null,
+                ),
+            )
+            coEvery {
+                accountsService.preLogin(email = EMAIL)
+            } returns PRE_LOGIN_SUCCESS.asSuccess()
+            coEvery {
+                identityService.getToken(
+                    email = EMAIL,
+                    authModel = IdentityTokenAuthModel.MasterPassword(
+                        username = EMAIL,
+                        password = PASSWORD_HASH,
+                    ),
+                    captchaToken = null,
+                    uniqueAppId = UNIQUE_APP_ID,
+                )
+            } returns successResponse.asSuccess()
+            coEvery { vaultRepository.syncIfNecessary() } just runs
+            every {
+                successResponse.toUserState(
+                    previousUserState = null,
+                    environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
+                )
+            } returns SINGLE_USER_STATE_1
+            val result = repository.login(
+                email = EMAIL,
+                password = PASSWORD,
+                captchaToken = null,
+            )
+            assertEquals(LoginResult.Success, result)
+            assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value)
+            coVerify { accountsService.preLogin(email = EMAIL) }
+            fakeAuthDiskSource.assertMasterPasswordHash(
+                userId = USER_ID_1,
+                passwordHash = PASSWORD_HASH,
+            )
+            coVerify {
+                identityService.getToken(
+                    email = EMAIL,
+                    authModel = IdentityTokenAuthModel.MasterPassword(
+                        username = EMAIL,
+                        password = PASSWORD_HASH,
+                    ),
+                    captchaToken = null,
+                    uniqueAppId = UNIQUE_APP_ID,
+                )
+                vaultRepository.syncIfNecessary()
+            }
+            assertEquals(
+                SINGLE_USER_STATE_1,
+                fakeAuthDiskSource.userState,
+            )
+            verify { settingsRepository.setDefaultsIfNecessary(userId = USER_ID_1) }
+            coVerify(exactly = 0) {
+                vaultRepository.unlockVault(
+                    userId = USER_ID_1,
+                    email = EMAIL,
+                    kdf = ACCOUNT_1.profile.toSdkParams(),
+                    userKey = any(),
+                    privateKey = any(),
+                    organizationKeys = null,
+                    masterPassword = PASSWORD,
+                )
+            }
+        }
+
     @Suppress("MaxLineLength")
     @Test
     fun `login get token succeeds when there is an existing user should switch to the new logged in user and lock the old user's vault`() =
@@ -897,8 +974,8 @@ class AuthRepositoryTest {
                     userId = USER_ID_1,
                     email = EMAIL,
                     kdf = ACCOUNT_1.profile.toSdkParams(),
-                    userKey = successResponse.key,
-                    privateKey = successResponse.privateKey,
+                    userKey = successResponse.key!!,
+                    privateKey = successResponse.privateKey!!,
                     organizationKeys = null,
                     masterPassword = PASSWORD,
                 )
@@ -938,8 +1015,8 @@ class AuthRepositoryTest {
                     userId = USER_ID_1,
                     email = EMAIL,
                     kdf = ACCOUNT_1.profile.toSdkParams(),
-                    userKey = successResponse.key,
-                    privateKey = successResponse.privateKey,
+                    userKey = successResponse.key!!,
+                    privateKey = successResponse.privateKey!!,
                     organizationKeys = null,
                     masterPassword = PASSWORD,
                 )
@@ -1083,8 +1160,8 @@ class AuthRepositoryTest {
                 userId = USER_ID_1,
                 email = EMAIL,
                 kdf = ACCOUNT_1.profile.toSdkParams(),
-                userKey = successResponse.key,
-                privateKey = successResponse.privateKey,
+                userKey = successResponse.key!!,
+                privateKey = successResponse.privateKey!!,
                 organizationKeys = null,
                 masterPassword = PASSWORD,
             )
@@ -1139,8 +1216,8 @@ class AuthRepositoryTest {
                 userId = USER_ID_1,
                 email = EMAIL,
                 kdf = ACCOUNT_1.profile.toSdkParams(),
-                userKey = successResponse.key,
-                privateKey = successResponse.privateKey,
+                userKey = successResponse.key!!,
+                privateKey = successResponse.privateKey!!,
                 organizationKeys = null,
                 masterPassword = PASSWORD,
             )
@@ -1179,8 +1256,8 @@ class AuthRepositoryTest {
                 userId = USER_ID_1,
                 email = EMAIL,
                 kdf = ACCOUNT_1.profile.toSdkParams(),
-                userKey = successResponse.key,
-                privateKey = successResponse.privateKey,
+                userKey = successResponse.key!!,
+                privateKey = successResponse.privateKey!!,
                 organizationKeys = null,
                 masterPassword = PASSWORD,
             )
@@ -1319,12 +1396,12 @@ class AuthRepositoryTest {
                     userId = USER_ID_1,
                     email = EMAIL,
                     kdf = ACCOUNT_1.profile.toSdkParams(),
-                    privateKey = successResponse.privateKey,
+                    privateKey = successResponse.privateKey!!,
                     organizationKeys = null,
                     initUserCryptoMethod = InitUserCryptoMethod.AuthRequest(
                         requestPrivateKey = DEVICE_REQUEST_PRIVATE_KEY,
                         method = AuthRequestMethod.MasterKey(
-                            authRequestKey = successResponse.key,
+                            authRequestKey = successResponse.key!!,
                             protectedMasterKey = DEVICE_ASYMMETRICAL_KEY,
                         ),
                     ),
@@ -1365,12 +1442,12 @@ class AuthRepositoryTest {
                     userId = USER_ID_1,
                     email = EMAIL,
                     kdf = ACCOUNT_1.profile.toSdkParams(),
-                    privateKey = successResponse.privateKey,
+                    privateKey = successResponse.privateKey!!,
                     organizationKeys = null,
                     initUserCryptoMethod = InitUserCryptoMethod.AuthRequest(
                         requestPrivateKey = DEVICE_REQUEST_PRIVATE_KEY,
                         method = AuthRequestMethod.MasterKey(
-                            authRequestKey = successResponse.key,
+                            authRequestKey = successResponse.key!!,
                             protectedMasterKey = DEVICE_ASYMMETRICAL_KEY,
                         ),
                     ),
@@ -1539,12 +1616,12 @@ class AuthRepositoryTest {
                 userId = SINGLE_USER_STATE_1.activeUserId,
                 email = SINGLE_USER_STATE_1.activeAccount.profile.email,
                 kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams(),
-                privateKey = successResponse.privateKey,
+                privateKey = successResponse.privateKey!!,
                 initUserCryptoMethod = InitUserCryptoMethod.AuthRequest(
                     requestPrivateKey = DEVICE_REQUEST_PRIVATE_KEY,
                     method = AuthRequestMethod.MasterKey(
                         protectedMasterKey = DEVICE_ASYMMETRICAL_KEY,
-                        authRequestKey = successResponse.key,
+                        authRequestKey = successResponse.key!!,
                     ),
                 ),
                 organizationKeys = null,
@@ -1584,6 +1661,7 @@ class AuthRepositoryTest {
             ssoCodeVerifier = SSO_CODE_VERIFIER,
             ssoRedirectUri = SSO_REDIRECT_URI,
             captchaToken = null,
+            organizationIdentifier = ORGANIZATION_IDENTIFIER,
         )
         assertEquals(LoginResult.Error(errorMessage = null), result)
         assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value)
@@ -1628,6 +1706,7 @@ class AuthRepositoryTest {
             ssoCodeVerifier = SSO_CODE_VERIFIER,
             ssoRedirectUri = SSO_REDIRECT_URI,
             captchaToken = null,
+            organizationIdentifier = ORGANIZATION_IDENTIFIER,
         )
         assertEquals(LoginResult.Error(errorMessage = "mock_error_message"), result)
         assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value)
@@ -1675,6 +1754,7 @@ class AuthRepositoryTest {
                 ssoCodeVerifier = SSO_CODE_VERIFIER,
                 ssoRedirectUri = SSO_REDIRECT_URI,
                 captchaToken = null,
+                organizationIdentifier = ORGANIZATION_IDENTIFIER,
             )
             assertEquals(LoginResult.Success, result)
             assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value)
@@ -1742,6 +1822,7 @@ class AuthRepositoryTest {
                 ssoCodeVerifier = SSO_CODE_VERIFIER,
                 ssoRedirectUri = SSO_REDIRECT_URI,
                 captchaToken = null,
+                organizationIdentifier = ORGANIZATION_IDENTIFIER,
             )
 
             assertEquals(LoginResult.Success, result)
@@ -1795,6 +1876,7 @@ class AuthRepositoryTest {
             ssoCodeVerifier = SSO_CODE_VERIFIER,
             ssoRedirectUri = SSO_REDIRECT_URI,
             captchaToken = null,
+            organizationIdentifier = ORGANIZATION_IDENTIFIER,
         )
         assertEquals(LoginResult.CaptchaRequired(CAPTCHA_KEY), result)
         assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value)
@@ -1839,6 +1921,7 @@ class AuthRepositoryTest {
                 ssoCodeVerifier = SSO_CODE_VERIFIER,
                 ssoRedirectUri = SSO_REDIRECT_URI,
                 captchaToken = null,
+                organizationIdentifier = ORGANIZATION_IDENTIFIER,
             )
             assertEquals(LoginResult.TwoFactorRequired, result)
             assertEquals(
@@ -1889,6 +1972,7 @@ class AuthRepositoryTest {
             ssoCodeVerifier = SSO_CODE_VERIFIER,
             ssoRedirectUri = SSO_REDIRECT_URI,
             captchaToken = null,
+            organizationIdentifier = ORGANIZATION_IDENTIFIER,
         )
         assertEquals(LoginResult.TwoFactorRequired, firstResult)
         coVerify {
@@ -1977,6 +2061,7 @@ class AuthRepositoryTest {
             ssoCodeVerifier = SSO_CODE_VERIFIER,
             ssoRedirectUri = SSO_REDIRECT_URI,
             captchaToken = null,
+            organizationIdentifier = ORGANIZATION_IDENTIFIER,
         )
         assertEquals(LoginResult.Success, result)
         assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value)
@@ -2463,13 +2548,15 @@ class AuthRepositoryTest {
         fakeAuthDiskSource.userState = null
 
         val result = repository.setPassword(
-            organizationId = "organizationId",
+            organizationIdentifier = "organizationId",
             password = "password",
             passwordHint = "passwordHint",
         )
 
         assertEquals(SetPasswordResult.Error, result)
         fakeAuthDiskSource.assertMasterPasswordHash(userId = USER_ID_1, passwordHash = null)
+        fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = null)
+        fakeAuthDiskSource.assertUserKey(userId = USER_ID_1, userKey = null)
     }
 
     @Test
@@ -2486,13 +2573,15 @@ class AuthRepositoryTest {
         } returns Throwable("Fail").asFailure()
 
         val result = repository.setPassword(
-            organizationId = "organizationId",
+            organizationIdentifier = "organizationId",
             password = password,
             passwordHint = "passwordHint",
         )
 
         assertEquals(SetPasswordResult.Error, result)
         fakeAuthDiskSource.assertMasterPasswordHash(userId = USER_ID_1, passwordHash = null)
+        fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = null)
+        fakeAuthDiskSource.assertUserKey(userId = USER_ID_1, userKey = null)
     }
 
     @Test
@@ -2518,13 +2607,15 @@ class AuthRepositoryTest {
         } returns Throwable("Fail").asFailure()
 
         val result = repository.setPassword(
-            organizationId = "organizationId",
+            organizationIdentifier = "organizationId",
             password = password,
             passwordHint = "passwordHint",
         )
 
         assertEquals(SetPasswordResult.Error, result)
         fakeAuthDiskSource.assertMasterPasswordHash(userId = USER_ID_1, passwordHash = null)
+        fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = null)
+        fakeAuthDiskSource.assertUserKey(userId = USER_ID_1, userKey = null)
     }
 
     @Test
@@ -2532,7 +2623,7 @@ class AuthRepositoryTest {
         val password = "password"
         val passwordHash = "passwordHash"
         val passwordHint = "passwordHint"
-        val organizationId = "organizationIdentifier"
+        val organizationId = ORGANIZATION_IDENTIFIER
         val encryptedUserKey = "encryptedUserKey"
         val privateRsaKey = "privateRsaKey"
         val publicRsaKey = "publicRsaKey"
@@ -2574,13 +2665,15 @@ class AuthRepositoryTest {
         } returns Throwable("Fail").asFailure()
 
         val result = repository.setPassword(
-            organizationId = organizationId,
+            organizationIdentifier = organizationId,
             password = password,
             passwordHint = passwordHint,
         )
 
         assertEquals(SetPasswordResult.Error, result)
         fakeAuthDiskSource.assertMasterPasswordHash(userId = USER_ID_1, passwordHash = null)
+        fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = null)
+        fakeAuthDiskSource.assertUserKey(userId = USER_ID_1, userKey = null)
     }
 
     @Test
@@ -2588,7 +2681,7 @@ class AuthRepositoryTest {
         val password = "password"
         val passwordHash = "passwordHash"
         val passwordHint = "passwordHint"
-        val organizationId = "organizationIdentifier"
+        val organizationId = ORGANIZATION_IDENTIFIER
         val encryptedUserKey = "encryptedUserKey"
         val privateRsaKey = "privateRsaKey"
         val publicRsaKey = "publicRsaKey"
@@ -2630,13 +2723,16 @@ class AuthRepositoryTest {
         } returns Unit.asSuccess()
 
         val result = repository.setPassword(
-            organizationId = organizationId,
+            organizationIdentifier = organizationId,
             password = password,
             passwordHint = passwordHint,
         )
 
         assertEquals(SetPasswordResult.Success, result)
         fakeAuthDiskSource.assertMasterPasswordHash(userId = USER_ID_1, passwordHash = passwordHash)
+        fakeAuthDiskSource.assertPrivateKey(userId = USER_ID_1, privateKey = privateRsaKey)
+        fakeAuthDiskSource.assertUserKey(userId = USER_ID_1, userKey = encryptedUserKey)
+        fakeAuthDiskSource.assertUserState(SINGLE_USER_STATE_1_WITH_PASS)
     }
 
     @Test
@@ -3373,6 +3469,7 @@ class AuthRepositoryTest {
         private const val PRIVATE_KEY = "privateKey"
         private const val USER_ID_1 = "2a135b23-e1fb-42c9-bec3-573857bc8181"
         private const val USER_ID_2 = "b9d32ec0-6497-4582-9798-b350f53bfa02"
+        private const val ORGANIZATION_IDENTIFIER = "organizationIdentifier"
         private val ORGANIZATIONS = listOf(createMockOrganization(number = 0))
         private val TWO_FACTOR_AUTH_METHODS_DATA = mapOf(
             TwoFactorAuthMethod.EMAIL to JsonObject(
@@ -3460,6 +3557,20 @@ class AuthRepositoryTest {
                 USER_ID_1 to ACCOUNT_1,
             ),
         )
+        private val SINGLE_USER_STATE_1_WITH_PASS = UserStateJson(
+            activeUserId = USER_ID_1,
+            accounts = mapOf(
+                USER_ID_1 to ACCOUNT_1.copy(
+                    profile = ACCOUNT_1.profile.copy(
+                        userDecryptionOptions = UserDecryptionOptionsJson(
+                            hasMasterPassword = true,
+                            keyConnectorUserDecryptionOptions = null,
+                            trustedDeviceUserDecryptionOptions = null,
+                        ),
+                    ),
+                ),
+            ),
+        )
         private val SINGLE_USER_STATE_2 = UserStateJson(
             activeUserId = USER_ID_2,
             accounts = mapOf(
diff --git a/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/util/UserStateJsonExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/util/UserStateJsonExtensionsTest.kt
index e13605b1a..dd3946f4b 100644
--- a/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/util/UserStateJsonExtensionsTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/util/UserStateJsonExtensionsTest.kt
@@ -5,6 +5,8 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
 import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
 import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
 import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson
+import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorUserDecryptionOptionsJson
+import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceUserDecryptionOptionsJson
 import com.x8bit.bitwarden.data.auth.datasource.network.model.UserDecryptionOptionsJson
 import com.x8bit.bitwarden.data.auth.repository.model.Organization
 import com.x8bit.bitwarden.data.auth.repository.model.UserOrganizations
@@ -94,6 +96,114 @@ class UserStateJsonExtensionsTest {
         )
     }
 
+    @Test
+    fun `toUserStateJsonWithPassword should update correct account to set needsMasterPassword`() {
+        val originalProfile = AccountJson.Profile(
+            userId = "activeUserId",
+            email = "email",
+            isEmailVerified = true,
+            name = "name",
+            stamp = null,
+            organizationId = null,
+            avatarColorHex = null,
+            hasPremium = true,
+            forcePasswordResetReason = null,
+            kdfType = KdfTypeJson.ARGON2_ID,
+            kdfIterations = 600000,
+            kdfMemory = 16,
+            kdfParallelism = 4,
+            userDecryptionOptions = null,
+        )
+        val originalAccount = AccountJson(
+            profile = originalProfile,
+            tokens = mockk(),
+            settings = mockk(),
+        )
+        assertEquals(
+            UserStateJson(
+                activeUserId = "activeUserId",
+                accounts = mapOf(
+                    "activeUserId" to originalAccount.copy(
+                        profile = originalProfile.copy(
+                            userDecryptionOptions = UserDecryptionOptionsJson(
+                                hasMasterPassword = true,
+                                keyConnectorUserDecryptionOptions = null,
+                                trustedDeviceUserDecryptionOptions = null,
+                            ),
+                        ),
+                    ),
+                ),
+            ),
+            UserStateJson(
+                activeUserId = "activeUserId",
+                accounts = mapOf(
+                    "activeUserId" to originalAccount,
+                ),
+            )
+                .toUserStateJsonWithPassword(),
+        )
+    }
+
+    @Test
+    fun `toUserStateJsonWithPassword should preserve values of userDecryptionOptions`() {
+        val keyConnectorOptionsJson = KeyConnectorUserDecryptionOptionsJson("key")
+        val trustedDeviceOptionsJson = TrustedDeviceUserDecryptionOptionsJson(
+            encryptedPrivateKey = "encryptedPrivateKey",
+            encryptedUserKey = "encryptedUserKey",
+            hasAdminApproval = true,
+            hasLoginApprovingDevice = true,
+            hasManageResetPasswordPermission = true,
+        )
+        val originalProfile = AccountJson.Profile(
+            userId = "activeUserId",
+            email = "email",
+            isEmailVerified = true,
+            name = "name",
+            stamp = null,
+            organizationId = null,
+            avatarColorHex = null,
+            hasPremium = true,
+            forcePasswordResetReason = null,
+            kdfType = KdfTypeJson.ARGON2_ID,
+            kdfIterations = 600000,
+            kdfMemory = 16,
+            kdfParallelism = 4,
+            userDecryptionOptions = UserDecryptionOptionsJson(
+                hasMasterPassword = true,
+                keyConnectorUserDecryptionOptions = keyConnectorOptionsJson,
+                trustedDeviceUserDecryptionOptions = trustedDeviceOptionsJson,
+            ),
+        )
+        val originalAccount = AccountJson(
+            profile = originalProfile,
+            tokens = mockk(),
+            settings = mockk(),
+        )
+        assertEquals(
+            UserStateJson(
+                activeUserId = "activeUserId",
+                accounts = mapOf(
+                    "activeUserId" to originalAccount.copy(
+                        profile = originalProfile.copy(
+                            userDecryptionOptions = UserDecryptionOptionsJson(
+                                hasMasterPassword = true,
+                                keyConnectorUserDecryptionOptions = keyConnectorOptionsJson,
+                                trustedDeviceUserDecryptionOptions = trustedDeviceOptionsJson,
+                            ),
+                        ),
+                    ),
+                ),
+            ),
+            UserStateJson(
+                activeUserId = "activeUserId",
+                accounts = mapOf(
+                    "activeUserId" to originalAccount,
+                ),
+            )
+                .toUserStateJsonWithPassword(),
+        )
+    }
+
     @Test
     fun `toUserState should return the correct UserState for an unlocked vault`() {
         assertEquals(
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnScreenTest.kt
index 94991b13b..e1d590656 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnScreenTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnScreenTest.kt
@@ -30,6 +30,7 @@ import org.junit.jupiter.api.Assertions.assertEquals
 
 class EnterpriseSignOnScreenTest : BaseComposeTest() {
     private var onNavigateBackCalled = false
+    private var onNavigateToSetPasswordCalled = false
     private var twoFactorLoginEmail: String? = null
     private val mutableEventFlow = bufferedMutableSharedFlow<EnterpriseSignOnEvent>()
     private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
@@ -47,6 +48,7 @@ class EnterpriseSignOnScreenTest : BaseComposeTest() {
         composeTestRule.setContent {
             EnterpriseSignOnScreen(
                 onNavigateBack = { onNavigateBackCalled = true },
+                onNavigateToSetPassword = { onNavigateToSetPasswordCalled = true },
                 onNavigateToTwoFactorLogin = { twoFactorLoginEmail = it },
                 viewModel = viewModel,
                 intentManager = intentManager,
@@ -114,6 +116,12 @@ class EnterpriseSignOnScreenTest : BaseComposeTest() {
         }
     }
 
+    @Test
+    fun `NavigateToSetPassword should call onNavigateToSetPassword`() {
+        mutableEventFlow.tryEmit(EnterpriseSignOnEvent.NavigateToSetPassword)
+        assertTrue(onNavigateToSetPasswordCalled)
+    }
+
     @Test
     fun `NavigateToTwoFactorLogin should call onNavigateToTwoFactorLogin`() {
         val email = "test@example.com"
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnViewModelTest.kt
index 76cb1ab15..8856af73a 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnViewModelTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/enterprisesignon/EnterpriseSignOnViewModelTest.kt
@@ -306,8 +306,9 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
     @Test
     fun `ssoCallbackResultFlow Success with same state with login Error should show loading dialog then show an error`() =
         runTest {
+            val orgIdentifier = "Bitwarden"
             coEvery {
-                authRepository.login(any(), any(), any(), any(), any())
+                authRepository.login(any(), any(), any(), any(), any(), any())
             } returns LoginResult.Error(null)
 
             val viewModel = createViewModel(
@@ -321,6 +322,17 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
                     awaitItem(),
                 )
 
+                viewModel.trySendAction(
+                    EnterpriseSignOnAction.OrgIdentifierInputChange(orgIdentifier),
+                )
+
+                assertEquals(
+                    DEFAULT_STATE.copy(
+                        orgIdentifierInput = orgIdentifier,
+                    ),
+                    awaitItem(),
+                )
+
                 mutableSsoCallbackResultFlow.tryEmit(ssoCallbackResult)
 
                 assertEquals(
@@ -328,6 +340,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
                         dialogState = EnterpriseSignOnState.DialogState.Loading(
                             R.string.logging_in.asText(),
                         ),
+                        orgIdentifierInput = orgIdentifier,
                     ),
                     awaitItem(),
                 )
@@ -337,6 +350,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
                         dialogState = EnterpriseSignOnState.DialogState.Error(
                             message = R.string.login_sso_error.asText(),
                         ),
+                        orgIdentifierInput = orgIdentifier,
                     ),
                     awaitItem(),
                 )
@@ -349,6 +363,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
                     ssoCodeVerifier = "def",
                     ssoRedirectUri = "bitwarden://sso-callback",
                     captchaToken = null,
+                    organizationIdentifier = orgIdentifier,
                 )
             }
         }
@@ -358,7 +373,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
     fun `ssoCallbackResultFlow Success with same state with login Success should show loading dialog, hide it, and save org identifier`() =
         runTest {
             coEvery {
-                authRepository.login(any(), any(), any(), any(), any())
+                authRepository.login(any(), any(), any(), any(), any(), any())
             } returns LoginResult.Success
 
             coEvery {
@@ -402,6 +417,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
                     ssoCodeVerifier = "def",
                     ssoRedirectUri = "bitwarden://sso-callback",
                     captchaToken = null,
+                    organizationIdentifier = "Bitwarden",
                 )
             }
             coVerify(exactly = 1) {
@@ -414,7 +430,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
     fun `ssoCallbackResultFlow Success with same state with login CaptchaRequired should show loading dialog, hide it, and send NavigateToCaptcha event`() =
         runTest {
             coEvery {
-                authRepository.login(any(), any(), any(), any(), any())
+                authRepository.login(any(), any(), any(), any(), any(), any())
             } returns LoginResult.CaptchaRequired("captcha")
 
             val uri: Uri = mockk()
@@ -464,6 +480,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
                     ssoCodeVerifier = "def",
                     ssoRedirectUri = "bitwarden://sso-callback",
                     captchaToken = null,
+                    organizationIdentifier = "Bitwarden",
                 )
             }
         }
@@ -473,7 +490,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
     fun `ssoCallbackResultFlow Success with same state with login TwoFactorRequired should show loading dialog, hide it, and send NavigateToTwoFactorLogin event`() =
         runTest {
             coEvery {
-                authRepository.login(any(), any(), any(), any(), any())
+                authRepository.login(any(), any(), any(), any(), any(), any())
             } returns LoginResult.TwoFactorRequired
 
             val initialState = DEFAULT_STATE.copy(orgIdentifierInput = "Bitwarden")
@@ -518,6 +535,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
                     ssoCodeVerifier = "def",
                     ssoRedirectUri = "bitwarden://sso-callback",
                     captchaToken = null,
+                    organizationIdentifier = "Bitwarden",
                 )
             }
         }
@@ -545,7 +563,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
     @Test
     fun `captchaTokenResultFlow Success should update the state and attempt to login`() = runTest {
         coEvery {
-            authRepository.login(any(), any(), any(), any(), any())
+            authRepository.login(any(), any(), any(), any(), any(), any())
         } returns LoginResult.Success
 
         coEvery {
@@ -748,5 +766,6 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
             codeVerifier = "def",
         )
         private const val DEFAULT_EMAIL = "test@gmail.com"
+        private const val DEFAULT_ORG_ID = "orgId"
     }
 }
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordScreenTest.kt
new file mode 100644
index 000000000..33cd0fcd5
--- /dev/null
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordScreenTest.kt
@@ -0,0 +1,129 @@
+package com.x8bit.bitwarden.ui.auth.feature.setpassword
+
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.hasAnyAncestor
+import androidx.compose.ui.test.isDialog
+import androidx.compose.ui.test.isDisplayed
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTextInput
+import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
+import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
+import com.x8bit.bitwarden.ui.platform.base.util.asText
+import com.x8bit.bitwarden.ui.util.assertNoDialogExists
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.update
+import org.junit.Before
+import org.junit.Test
+
+class SetPasswordScreenTest : BaseComposeTest() {
+    private val mutableEventFlow = bufferedMutableSharedFlow<SetPasswordEvent>()
+    private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
+    val viewModel = mockk<SetPasswordViewModel>(relaxed = true) {
+        every { eventFlow } returns mutableEventFlow
+        every { stateFlow } returns mutableStateFlow
+    }
+
+    @Before
+    fun setUp() {
+        composeTestRule.setContent {
+            SetPasswordScreen(
+                viewModel = viewModel,
+            )
+        }
+    }
+
+    @Test
+    fun `basicDialog should update according to state`() {
+        composeTestRule
+            .onNodeWithText("Error message")
+            .assertDoesNotExist()
+        composeTestRule.assertNoDialogExists()
+
+        mutableStateFlow.update {
+            it.copy(
+                dialogState = SetPasswordState.DialogState.Error(
+                    title = null,
+                    message = "Error message".asText(),
+                ),
+            )
+        }
+
+        composeTestRule
+            .onNodeWithText("Error message")
+            .assert(hasAnyAncestor(isDialog()))
+            .isDisplayed()
+    }
+
+    @Test
+    fun `loadingDialog should update according to state`() {
+        composeTestRule.onNodeWithText("Loading...").assertDoesNotExist()
+
+        mutableStateFlow.update {
+            it.copy(
+                dialogState = SetPasswordState.DialogState.Loading(
+                    message = "Loading...".asText(),
+                ),
+            )
+        }
+
+        composeTestRule.onNodeWithText("Loading...").isDisplayed()
+    }
+
+    @Test
+    fun `cancel button click should emit CancelClick`() {
+        composeTestRule.onNodeWithText("Cancel").performClick()
+
+        verify {
+            viewModel.trySendAction(SetPasswordAction.CancelClick)
+        }
+    }
+
+    @Test
+    fun `submit button click should emit SubmitClick`() {
+        composeTestRule.onNodeWithText("Submit").performClick()
+
+        verify {
+            viewModel.trySendAction(SetPasswordAction.SubmitClick)
+        }
+    }
+
+    @Test
+    fun `password input change should send PasswordInputChange action`() {
+        val input = "Test123"
+        composeTestRule.onNodeWithText("Master password").performTextInput(input)
+        verify {
+            viewModel.trySendAction(SetPasswordAction.PasswordInputChanged("Test123"))
+        }
+    }
+
+    @Test
+    fun `retype password input change should send RetypePasswordInputChanged action`() {
+        val input = "Test123"
+        composeTestRule.onNodeWithText("Re-type master password").performTextInput(input)
+        verify {
+            viewModel.trySendAction(SetPasswordAction.RetypePasswordInputChanged("Test123"))
+        }
+    }
+
+    @Test
+    fun `password hint input change should send PasswordHintInputChanged action`() {
+        val input = "Test123"
+        composeTestRule.onNodeWithText("Master password hint (optional)").performTextInput(input)
+        verify {
+            viewModel.trySendAction(SetPasswordAction.PasswordHintInputChanged("Test123"))
+        }
+    }
+}
+
+private val DEFAULT_STATE = SetPasswordState(
+    dialogState = null,
+    organizationIdentifier = "SSO",
+    passwordHintInput = "",
+    passwordInput = "",
+    policies = emptyList(),
+    retypePasswordInput = "",
+)
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordViewModelTest.kt
new file mode 100644
index 000000000..ce327971e
--- /dev/null
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordViewModelTest.kt
@@ -0,0 +1,345 @@
+package com.x8bit.bitwarden.ui.auth.feature.setpassword
+
+import androidx.lifecycle.SavedStateHandle
+import app.cash.turbine.test
+import com.x8bit.bitwarden.R
+import com.x8bit.bitwarden.data.auth.repository.AuthRepository
+import com.x8bit.bitwarden.data.auth.repository.model.SetPasswordResult
+import com.x8bit.bitwarden.data.vault.repository.VaultRepository
+import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
+import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
+import com.x8bit.bitwarden.ui.platform.base.util.asText
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.runs
+import io.mockk.verify
+import kotlinx.coroutines.test.runTest
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+
+class SetPasswordViewModelTest : BaseViewModelTest() {
+    private val authRepository: AuthRepository = mockk {
+        every { passwordPolicies } returns emptyList()
+        every { organizationIdentifier } returns ORGANIZATION_IDENTIFIER
+    }
+    private val vaultRepository: VaultRepository = mockk()
+
+    @Test
+    fun `null organizationIdentifier logs user out`() = runTest {
+        every { authRepository.logout() } just runs
+        every { authRepository.organizationIdentifier } returns null
+        createViewModel()
+        verify { authRepository.logout() }
+    }
+
+    @Test
+    fun `CancelClick calls logout`() = runTest {
+        every { authRepository.logout() } just runs
+        val viewModel = createViewModel()
+        viewModel.trySendAction(SetPasswordAction.CancelClick)
+        verify { authRepository.logout() }
+    }
+
+    @Test
+    fun `SubmitClicked with blank password shows error alert`() = runTest {
+        val viewModel = createViewModel()
+        viewModel.trySendAction(SetPasswordAction.SubmitClick)
+
+        assertEquals(
+            DEFAULT_STATE.copy(
+                dialogState = SetPasswordState.DialogState.Error(
+                    title = R.string.an_error_has_occurred.asText(),
+                    message = R.string.validation_field_required
+                        .asText(R.string.master_password.asText()),
+                ),
+            ),
+            viewModel.stateFlow.value,
+        )
+
+        // Dismiss the alert.
+        viewModel.trySendAction(SetPasswordAction.DialogDismiss)
+        assertEquals(
+            DEFAULT_STATE,
+            viewModel.stateFlow.value,
+        )
+    }
+
+    @Test
+    fun `SubmitClicked with invalid password shows error alert for short password`() = runTest {
+        val password = "TestPass"
+
+        val viewModel = createViewModel()
+        viewModel.trySendAction(SetPasswordAction.PasswordInputChanged(password))
+        viewModel.trySendAction(SetPasswordAction.RetypePasswordInputChanged(password))
+        viewModel.trySendAction(SetPasswordAction.SubmitClick)
+
+        assertEquals(
+            DEFAULT_STATE.copy(
+                dialogState = SetPasswordState.DialogState.Error(
+                    title = R.string.an_error_has_occurred.asText(),
+                    message = R.string.master_password_length_val_message_x
+                        .asText(MIN_PASSWORD_LENGTH),
+                ),
+                passwordInput = password,
+                retypePasswordInput = password,
+            ),
+            viewModel.stateFlow.value,
+        )
+    }
+
+    @Test
+    fun `SubmitClicked with non-matching retyped password shows error alert`() = runTest {
+        val password = "TestPassword123"
+        coEvery {
+            authRepository.validatePasswordAgainstPolicies(password)
+        } returns true
+
+        val viewModel = createViewModel()
+        viewModel.trySendAction(SetPasswordAction.PasswordInputChanged(password))
+
+        viewModel.trySendAction(SetPasswordAction.SubmitClick)
+
+        assertEquals(
+            DEFAULT_STATE.copy(
+                dialogState = SetPasswordState.DialogState.Error(
+                    title = R.string.an_error_has_occurred.asText(),
+                    message = R.string.master_password_confirmation_val_message.asText(),
+                ),
+                passwordInput = password,
+            ),
+            viewModel.stateFlow.value,
+        )
+    }
+
+    @Test
+    fun `SubmitClicked with invalid password shows error alert for weak password reason`() =
+        runTest {
+            val password = "Test123"
+            coEvery {
+                authRepository.validatePasswordAgainstPolicies(password)
+            } returns false
+
+            val viewModel = createViewModel(
+                state = SetPasswordState(
+                    organizationIdentifier = ORGANIZATION_IDENTIFIER,
+                    policies = listOf(
+                        R.string.policy_in_effect_uppercase.asText(),
+                    ),
+                    dialogState = null,
+                    passwordInput = "",
+                    retypePasswordInput = "",
+                    passwordHintInput = "",
+                ),
+            )
+
+            viewModel.trySendAction(SetPasswordAction.PasswordInputChanged(password))
+            viewModel.trySendAction(SetPasswordAction.RetypePasswordInputChanged(password))
+            viewModel.trySendAction(SetPasswordAction.SubmitClick)
+
+            assertEquals(
+                DEFAULT_STATE.copy(
+                    dialogState = SetPasswordState.DialogState.Error(
+                        title = R.string.master_password_policy_validation_title.asText(),
+                        message = R.string.master_password_policy_validation_message.asText(),
+                    ),
+                    passwordInput = password,
+                    retypePasswordInput = password,
+                    policies = listOf(
+                        R.string.policy_in_effect_uppercase.asText(),
+                    ),
+                ),
+                viewModel.stateFlow.value,
+            )
+            coVerify {
+                authRepository.validatePasswordAgainstPolicies(password)
+            }
+        }
+
+    @Test
+    fun `SubmitClicked with all valid inputs and unlock vault success sets password`() = runTest {
+        val password = "TestPassword123"
+        coEvery {
+            authRepository.setPassword(
+                organizationIdentifier = ORGANIZATION_IDENTIFIER,
+                password = password,
+                passwordHint = "",
+            )
+        } returns SetPasswordResult.Success
+        coEvery {
+            vaultRepository.unlockVaultWithMasterPassword(password)
+        } returns VaultUnlockResult.Success
+
+        val viewModel = createViewModel()
+        viewModel.trySendAction(SetPasswordAction.PasswordInputChanged(password))
+        viewModel.trySendAction(SetPasswordAction.RetypePasswordInputChanged(password))
+
+        viewModel.stateFlow.test {
+            assertEquals(
+                DEFAULT_STATE.copy(
+                    dialogState = null,
+                    passwordInput = password,
+                    retypePasswordInput = password,
+                ),
+                awaitItem(),
+            )
+
+            viewModel.trySendAction(SetPasswordAction.SubmitClick)
+
+            assertEquals(
+                DEFAULT_STATE.copy(
+                    dialogState = SetPasswordState.DialogState.Loading(
+                        message = R.string.updating_password.asText(),
+                    ),
+                    passwordInput = password,
+                    retypePasswordInput = password,
+                ),
+                awaitItem(),
+            )
+
+            assertEquals(
+                DEFAULT_STATE.copy(
+                    dialogState = null,
+                    passwordInput = password,
+                    retypePasswordInput = password,
+                ),
+                awaitItem(),
+            )
+        }
+
+        coVerify {
+            authRepository.setPassword(
+                organizationIdentifier = ORGANIZATION_IDENTIFIER,
+                password = password,
+                passwordHint = "",
+            )
+            vaultRepository.unlockVaultWithMasterPassword(password)
+        }
+    }
+
+    @Test
+    fun `SubmitClicked with all valid inputs and unlock vault failure shows error`() = runTest {
+        val password = "TestPassword123"
+        coEvery {
+            authRepository.setPassword(
+                organizationIdentifier = ORGANIZATION_IDENTIFIER,
+                password = password,
+                passwordHint = "",
+            )
+        } returns SetPasswordResult.Success
+        coEvery {
+            vaultRepository.unlockVaultWithMasterPassword(password)
+        } returns VaultUnlockResult.InvalidStateError
+
+        val viewModel = createViewModel()
+        viewModel.trySendAction(SetPasswordAction.PasswordInputChanged(password))
+        viewModel.trySendAction(SetPasswordAction.RetypePasswordInputChanged(password))
+
+        viewModel.stateFlow.test {
+            assertEquals(
+                DEFAULT_STATE.copy(
+                    dialogState = null,
+                    passwordInput = password,
+                    retypePasswordInput = password,
+                ),
+                awaitItem(),
+            )
+
+            viewModel.trySendAction(SetPasswordAction.SubmitClick)
+
+            assertEquals(
+                DEFAULT_STATE.copy(
+                    dialogState = SetPasswordState.DialogState.Loading(
+                        message = R.string.updating_password.asText(),
+                    ),
+                    passwordInput = password,
+                    retypePasswordInput = password,
+                ),
+                awaitItem(),
+            )
+
+            assertEquals(
+                DEFAULT_STATE.copy(
+                    dialogState = SetPasswordState.DialogState.Error(
+                        title = R.string.an_error_has_occurred.asText(),
+                        message = R.string.generic_error_message.asText(),
+                    ),
+                    passwordInput = password,
+                    retypePasswordInput = password,
+                ),
+                awaitItem(),
+            )
+        }
+
+        coVerify {
+            authRepository.setPassword(
+                organizationIdentifier = ORGANIZATION_IDENTIFIER,
+                password = password,
+                passwordHint = "",
+            )
+            vaultRepository.unlockVaultWithMasterPassword(password)
+        }
+    }
+
+    @Test
+    fun `PasswordInputChanged should update the password input in the state`() = runTest {
+        val viewModel = createViewModel()
+        viewModel.trySendAction(SetPasswordAction.PasswordInputChanged("TestPassword123"))
+
+        assertEquals(
+            DEFAULT_STATE.copy(
+                passwordInput = "TestPassword123",
+            ),
+            viewModel.stateFlow.value,
+        )
+    }
+
+    @Test
+    fun `RetypePasswordInputChanged should update the retype password input in the state`() =
+        runTest {
+            val viewModel = createViewModel()
+            viewModel.trySendAction(SetPasswordAction.RetypePasswordInputChanged("TestPassword123"))
+
+            assertEquals(
+                DEFAULT_STATE.copy(
+                    retypePasswordInput = "TestPassword123",
+                ),
+                viewModel.stateFlow.value,
+            )
+        }
+
+    @Test
+    fun `PasswordHintInputChanged should update the password hint input in the state`() = runTest {
+        val viewModel = createViewModel()
+        viewModel.trySendAction(SetPasswordAction.PasswordHintInputChanged("TestPassword123"))
+
+        assertEquals(
+            DEFAULT_STATE.copy(
+                passwordHintInput = "TestPassword123",
+            ),
+            viewModel.stateFlow.value,
+        )
+    }
+
+    private fun createViewModel(
+        state: SetPasswordState? = null,
+    ): SetPasswordViewModel =
+        SetPasswordViewModel(
+            authRepository = authRepository,
+            vaultRepository = vaultRepository,
+            savedStateHandle = SavedStateHandle(mapOf("state" to state)),
+        )
+}
+
+private const val MIN_PASSWORD_LENGTH = 12
+private const val ORGANIZATION_IDENTIFIER: String = "orgId"
+private val DEFAULT_STATE = SetPasswordState(
+    organizationIdentifier = ORGANIZATION_IDENTIFIER,
+    policies = emptyList(),
+    dialogState = null,
+    passwordInput = "",
+    retypePasswordInput = "",
+    passwordHintInput = "",
+)
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreenTest.kt
index 2c169f19f..e68f825b7 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreenTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreenTest.kt
@@ -91,6 +91,15 @@ class RootNavScreenTest : BaseComposeTest() {
             )
         }
 
+        // Make sure navigating to set password works as expected:
+        rootNavStateFlow.value = RootNavState.SetPassword
+        composeTestRule.runOnIdle {
+            fakeNavHostController.assertLastNavigation(
+                route = "set_password",
+                navOptions = expectedNavOptions,
+            )
+        }
+
         // Make sure navigating to vault unlocked works as expected:
         rootNavStateFlow.value = RootNavState.VaultUnlocked(activeUserId = "userId")
         composeTestRule.runOnIdle {
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModelTest.kt
index 97e7fc206..d5c2dd876 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModelTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModelTest.kt
@@ -86,6 +86,36 @@ class RootNavViewModelTest : BaseViewModelTest() {
         assertEquals(RootNavState.ResetPassword, viewModel.stateFlow.value)
     }
 
+    @Test
+    fun `when the active user needs a master password the nav state should be SetPassword`() {
+        mutableUserStateFlow.tryEmit(
+            UserState(
+                activeUserId = "activeUserId",
+                accounts = listOf(
+                    UserState.Account(
+                        userId = "activeUserId",
+                        name = "name",
+                        email = "email",
+                        avatarColorHex = "avatarColorHex",
+                        environment = Environment.Us,
+                        isPremium = true,
+                        isLoggedIn = false,
+                        isVaultUnlocked = false,
+                        needsPasswordReset = true,
+                        isBiometricsEnabled = false,
+                        organizations = emptyList(),
+                        needsMasterPassword = true,
+                    ),
+                ),
+            ),
+        )
+        val viewModel = createViewModel()
+        assertEquals(
+            RootNavState.SetPassword,
+            viewModel.stateFlow.value,
+        )
+    }
+
     @Suppress("MaxLineLength")
     @Test
     fun `when the active user but there are pending account additions the nav state should be Auth`() {