SSO Account Create failure
- Dominant language
- Kotlin
- Stars
- 9.4k
- Forks
- 1.1k
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 67
Description
### Steps To Reproduce
1. Have a SSO Organization with validated domain
2. Try to create an account via the Android app
It flows to the SSO Login, and then nicely to the set password interface.
4. Submit the password form
5. See the error
Side note: This was tested on a Self-Hosted v2026.3.1 and do not have access to the system to update to v2026.4.0, but i think the bug is client-side and not server-side anyways.
### Expected Result
Being able to correctly continue after setting the password.
The same flow does work without issues via the web interface, but the Android app expects `MasterPasswordUnlock` data somewhere, which it did not received from the server, or stored it self in a known location.
### Actual Result
Error from the app.
```
Stacktrace:
com.bitwarden.core.data.repository.error.MissingPropertyException: Missing the required MasterPasswordUnlock data property
com.x8bit.bitwarden.data.vault.repository.VaultRepositoryImpl.unlockVaultWithMasterPassword(VaultRepositoryImpl.kt:354)
com.x8bit.bitwarden.data.auth.repository.AuthRepositoryImpl.setPassword(AuthRepositoryImpl.kt:1183)
com.x8bit.bitwarden.data.auth.repository.AuthRepositoryImpl$setPassword$1.invokeSuspend(Unknown Source:15)
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
android.os.Handler.handleCallback(Handler.java:942)
android.os.Handler.dispatchMessage(Handler.java:99)
android.os.Looper.loopOnce(Looper.java:201)
android.os.Looper.loop(Looper.java:288)
android.app.ActivityThread.main(ActivityThread.java:7924)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:937)
Version: 2026.4.0 (21433)
CI: 🧱 commit: bitwarden/android/main@61955d7cbe1546a4c467284b4a5491d4f0e47afa
💻 build source: bitwarden/android/actions/runs/23950989427/attempts/1
```
This also happens with `v2026.3.1 (21415)`
### Screenshots or Videos
_No response_
### Additional Context
I asked AI to check and verify, and it came with the following response.
I have no clue if this is correct at all, but maybe it helps.
AI Response
## Bug Confirmed: Missing `masterPasswordUnlock` Update After `setPassword`
The root cause is a clear logic gap in `AuthRepositoryImpl.setPassword()`. The flow calls `unlockVaultWithMasterPassword` after the API succeeds, but the data that method depends on was never written to disk.
## The Execution Path
Here's what happens step by step:
1. **`AuthRepositoryImpl.setPassword()`** (line 1111) calls `accountsService.setPassword(...)` — the API returns HTTP 200 with **no body**, as expected.
2. In the `.onSuccess {}` block (line ~1168), the code stores two things:
```kotlin
authDiskSource.storePrivateKey(userId = userId, privateKey = it) // optional
authDiskSource.storeUserKey(userId = userId, userKey = encryptedUserKey)
```
3. It then proceeds to call `vaultRepository.unlockVaultWithMasterPassword(password)` at line 1183.
## The Actual Failure Point
Inside `VaultRepositoryImpl.unlockVaultWithMasterPassword()` (line ~342):
```kotlin
val masterPasswordUnlock = activeAccount
?.profile
?.userDecryptionOptions
?.masterPasswordUnlock
?: return VaultUnlockResult.InvalidStateError(
error = MissingPropertyException("MasterPasswordUnlock data"), // ← THROWN HERE
)
```
This reads `userDecryptionOptions.masterPasswordUnlock` from the **stored disk profile**. For TDE users or SSO-without-password users going through the `setPassword` flow, this field was **`null` at login time** (they had no master password). Nothing in `setPassword`'s success path ever updates it, so it remains `null` even after the API call succeeds.
The `storeUserKey()` call writes the encrypted user key to its own separate slot in `AuthDiskSource`, but `unlockVaultWithMasterPassword` doesn't read from that slot — it reads from `userDecryptionOptions.masterPasswordUnlock.masterKeyWrappedUserKey`, which is a completely different field in the profile JSON.
## What the Fix Should Be
In the `.onSuccess {}` block of `accountsService.setPassword(...)`, after storing the user key, the code must also **update `userDecryptionOptions.masterPasswordUnlock`** in the stored `userState` with the newly created data. The `MasterPasswordUnlockDataJson` model requires three fields that are all available at this point:
```kotlin
.onSuccess {
rsaKeys?.private?.let {
authDiskSource.storePrivateKey(userId = userId, privateKey = it)
}
authDiskSource.storeUserKey(userId = userId, userKey = encryptedUserKey)
// ✅ FIX: Patch userState to populate masterPasswordUnlock so
// unlockVaultWithMasterPassword can proceed after this setPassword call.
authDiskSource.userState = authDiskSource.userState?.let { userState ->
val updatedAccount = userState.activeAccount?.let { account ->
val newMasterPasswordUnlock = MasterPasswordUnlockDataJson(
salt = account.profile.email,
kdf = account.profile.toKdfJson(), // already available on profile
masterKeyWrappedUserKey = encryptedUserKey,
)
account.copy(
profile = account.profile.copy(
userDecryptionOptions = account.profile.userDecryptionOptions
?.copy(masterPasswordUnlock = newMasterPasswordUnlock)
?: UserDecryptionOptionsJson(
hasMasterPassword = true,
masterPasswordUnlock = newMasterPasswordUnlock,
// ... other fields as null/defaults
),
),
)
}
userState.copy(
accounts = userState.accounts
.toMutableMap()
.apply { updatedAccount?.let { put(userId, it) } },
)
}
}
```
## Summary
| What the code does | What it should do |
| :-- | :-- |
| Stores `encryptedUserKey` in its own disk slot | Also update `userDecryptionOptions.masterPasswordUnlock` in the profile |
| Calls `unlockVaultWithMasterPassword` expecting the profile to have the data | Profile still has `masterPasswordUnlock = null` from the original TDE/SSO login |
| Crashes with `MissingPropertyException` | Would read the freshly written `masterPasswordUnlock` and unlock successfully |
The API returning a bare `200` is correct and not the issue. The client simply fails to **synthesize and persist** the `masterPasswordUnlock` block after successfully setting the password, which is the data `unlockVaultWithMasterPassword` unconditionally requires before it can initialize the vault crypto.
### Build Version
2026.4.0 (21433)
### What server are you connecting to?
Self-host
### Self-host Server Version
2026.3.1 (I'm not able to update it my self)
### Environment Details
Any Android, doesn't matter tried several
### Issue Tracking Info
- [x] I understand that work is tracked outside of Github. A PR will be linked to this issue should one be opened to address it, but Bitwarden doesn't use fields like "assigned", "milestone", or "project" to track progress.
Contributor guide
Assessment
This issue has not been assessed yet.