vaultwarden |[2025-08-09 10:20:02.368+0800][request][INFO] GET /api/devices/knowndevice
vaultwarden |[2025-08-09 10:20:02.369+0800][response][INFO](get_known_device) GET /api/devices/knowndevice => 200 OK
vaultwarden |[2025-08-09 10:20:04.400+0800][request][INFO] GET /api/devices/knowndevice
vaultwarden |[2025-08-09 10:20:04.401+0800][response][INFO](get_known_device) GET /api/devices/knowndevice => 200 OK
vaultwarden |[2025-08-09 10:20:06.609+0800][request][INFO] GET /api/devices/knowndevice
vaultwarden |[2025-08-09 10:20:06.610+0800][response][INFO](get_known_device) GET /api/devices/knowndevice => 200 OK
vaultwarden |[2025-08-09 10:20:09.069+0800][request][INFO] GET /api/devices/knowndevice
vaultwarden |[2025-08-09 10:20:09.070+0800][response][INFO](get_known_device) GET /api/devices/knowndevice => 200 OK
vaultwarden |[2025-08-09 10:20:15.308+0800][request][INFO] POST /identity/accounts/prelogin
vaultwarden |[2025-08-09 10:20:15.309+0800][response][INFO](prelogin) POST /identity/accounts/prelogin => 200 OK
vaultwarden |[2025-08-09 10:20:15.668+0800][request][INFO] POST /identity/connect/token
vaultwarden |[2025-08-09 10:20:15.986+0800][error][ERROR] Serde.
vaultwarden |[CAUSE] Error("missing field `type_`", line: 1, column: 607)
vaultwarden |[2025-08-09 10:20:15.986+0800][response][INFO](login) POST /identity/connect/token => 400 Bad Request
@IamTaoChen commented on GitHub (Aug 9, 2025):
It cannot downgrade to `lastest`
```bash
vaultwarden | [2025-08-09 10:20:02.368+0800][request][INFO] GET /api/devices/knowndevice
vaultwarden | [2025-08-09 10:20:02.369+0800][response][INFO] (get_known_device) GET /api/devices/knowndevice => 200 OK
vaultwarden | [2025-08-09 10:20:04.400+0800][request][INFO] GET /api/devices/knowndevice
vaultwarden | [2025-08-09 10:20:04.401+0800][response][INFO] (get_known_device) GET /api/devices/knowndevice => 200 OK
vaultwarden | [2025-08-09 10:20:06.609+0800][request][INFO] GET /api/devices/knowndevice
vaultwarden | [2025-08-09 10:20:06.610+0800][response][INFO] (get_known_device) GET /api/devices/knowndevice => 200 OK
vaultwarden | [2025-08-09 10:20:09.069+0800][request][INFO] GET /api/devices/knowndevice
vaultwarden | [2025-08-09 10:20:09.070+0800][response][INFO] (get_known_device) GET /api/devices/knowndevice => 200 OK
vaultwarden | [2025-08-09 10:20:15.308+0800][request][INFO] POST /identity/accounts/prelogin
vaultwarden | [2025-08-09 10:20:15.309+0800][response][INFO] (prelogin) POST /identity/accounts/prelogin => 200 OK
vaultwarden | [2025-08-09 10:20:15.668+0800][request][INFO] POST /identity/connect/token
vaultwarden | [2025-08-09 10:20:15.986+0800][error][ERROR] Serde.
vaultwarden | [CAUSE] Error("missing field `type_`", line: 1, column: 607)
vaultwarden | [2025-08-09 10:20:15.986+0800][response][INFO] (login) POST /identity/connect/token => 400 Bad Request
```
You need to either remove 2fa, use a different 2fa type, or restore the record from a backup.
I'm not seeing a failed attempt using webauthn in de first log. I only see duo there. So, not sure what you tried there.
@BlackDex commented on GitHub (Aug 9, 2025):
You need to either remove 2fa, use a different 2fa type, or restore the record from a backup.
I'm not seeing a failed attempt using webauthn in de first log. I only see duo there. So, not sure what you tried there.
I have the same Error, but only if I use the WebAuthn Passkey from my Bitwarden Account in Bitwarden. If I use my YubiKey it works.
Edit: It also happens with the yubikey here the log:
vaultwarden | [2025-08-09 13:17:56.930][request][INFO] POST /identity/connect/tokenvaultwarden | [2025-08-09 13:17:56.947][webauthn_rs_core::core][ERROR] Credential backup elligibility has changed!vaultwarden | [2025-08-09 13:17:56.947][error][ERROR] Webauthn.vaultwarden | [CAUSE] CredentialBackupElligibilityInconsistentvaultwarden | [2025-08-09 13:17:56.947][response][INFO] (login) POST /identity/connect/token => 400 Bad Requestvaultwarden | [2025-08-09 13:18:45.495][rocket::request::request][WARN] Header 'cookie' contains invalid UTF-8vaultwarden | [2025-08-09 13:18:45.495][rocket::request::request::_][WARN] Rocket only supports UTF-8 header values. Dropping header.vaultwarden | [2025-08-09 13:18:45.496][request][INFO] POST /identity/connect/tokenvaultwarden | [2025-08-09 13:18:45.500][vaultwarden::api::core::two_factor::webauthn][ERROR] Can't recover login challengevaultwarden | [2025-08-09 13:18:45.500][response][INFO] (login) POST /identity/connect/token => 400 Bad Request
@Blacks-Army commented on GitHub (Aug 9, 2025):
I have the same Error, but only if I use the WebAuthn Passkey from my Bitwarden Account in Bitwarden. If I use my YubiKey it works.
Edit: It also happens with the yubikey here the log:
```yaml
vaultwarden | [2025-08-09 13:17:56.930][request][INFO] POST /identity/connect/token
vaultwarden | [2025-08-09 13:17:56.947][webauthn_rs_core::core][ERROR] Credential backup elligibility has changed!
vaultwarden | [2025-08-09 13:17:56.947][error][ERROR] Webauthn.
vaultwarden | [CAUSE] CredentialBackupElligibilityInconsistent
vaultwarden | [2025-08-09 13:17:56.947][response][INFO] (login) POST /identity/connect/token => 400 Bad Request
vaultwarden | [2025-08-09 13:18:45.495][rocket::request::request][WARN] Header 'cookie' contains invalid UTF-8
vaultwarden | [2025-08-09 13:18:45.495][rocket::request::request::_][WARN] Rocket only supports UTF-8 header values. Dropping header.
vaultwarden | [2025-08-09 13:18:45.496][request][INFO] POST /identity/connect/token
vaultwarden | [2025-08-09 13:18:45.500][vaultwarden::api::core::two_factor::webauthn][ERROR] Can't recover login challenge
vaultwarden | [2025-08-09 13:18:45.500][response][INFO] (login) POST /identity/connect/token => 400 Bad Request
```
I see strange stuff regarding non-utf-8 characters in your logs. Those shouldn't be happening at all. That might also break the WebauthN data sent to the server.
Not sure what is in between the browser and the Vaultwarden instance, but it looks like it is adjusting your WebAuthN response to the server.
@BlackDex commented on GitHub (Aug 9, 2025):
I see strange stuff regarding non-utf-8 characters in your logs. Those shouldn't be happening at all. That might also break the WebauthN data sent to the server.
Not sure what is in between the browser and the Vaultwarden instance, but it looks like it is adjusting your WebAuthN response to the server.
I am using Traefik, but this also did not happened before. Traefik only sets Headers and Rate Limits.
Edit: And I am not setting a Header named cookie
@Blacks-Army commented on GitHub (Aug 9, 2025):
I am using Traefik, but this also did not happened before. Traefik only sets Headers and Rate Limits.
Edit: And I am not setting a Header named cookie
I used the current stable version to create the 2FA tokens, and i used both SoftU2F and my YubiKey5c. Then compiled the main branch, and logged in just fine.
Be sure you have HTTPS working with a valid certificate. Check /admin/diagnostics to see if there are issues.
@IamTaoChen i would suggest to make the admin interface available so that you can also check the diagnostics page. It provides us useful information which otherwise makes it almost impossible for us to help in a useful way.
@Blacks-Army, In your case it looks like it has something to do with a backup key. Not sure which key you are using right now, is that the key used during the creation, or are you now using the backup key to verify.
I noticed something similar in Proxmox regarding this message. But, i do not have the means to test this my self, as i do not have two YubiKey's.
@BlackDex commented on GitHub (Aug 9, 2025):
I'm unable to reproduce this on my side.
I used the current stable version to create the 2FA tokens, and i used both SoftU2F and my YubiKey5c. Then compiled the main branch, and logged in just fine.
Be sure you have HTTPS working with a valid certificate. Check `/admin/diagnostics` to see if there are issues.
@IamTaoChen i would suggest to make the admin interface available so that you can also check the diagnostics page. It provides us useful information which otherwise makes it almost impossible for us to help in a useful way.
@Blacks-Army, In your case it looks like it has something to do with a backup key. Not sure which key you are using right now, is that the key used during the creation, or are you now using the backup key to verify.
I noticed something similar in Proxmox regarding this message. But, i do not have the means to test this my self, as i do not have two YubiKey's.
@BlackDex commented on GitHub (Aug 9, 2025):
As a reference to where i found it at Proxmox: https://git.proxmox.com/?p=proxmox.git;a=commit;h=e4d5cce8ec7b97353b4c922424bf1bc4dcede420
@zUnixorn, maybe you have an idea regarding this?
Also, looking at the code and description from YubiKey it self. It might be you registered the spare key (which you might be using right now) after you verified the main key in Vaultwarden. If that is the case, then i think this error is legit, and increases security of Vaultwarden. I think the previous implementation just didn't bothered looking at it, and that is why you were able to use it and login.
@BlackDex commented on GitHub (Aug 9, 2025):
Also, looking at the code and description from YubiKey it self. It might be you registered the spare key (which you might be using right now) after you verified the main key in Vaultwarden. If that is the case, then i think this error is legit, and increases security of Vaultwarden. I think the previous implementation just didn't bothered looking at it, and that is why you were able to use it and login.
API calls:
Header: 'x-xss-protection' does not contain '0'
2FA Connector calls:
Header: 'x-xss-protection' does not contain '0'
Header: 'x-frame-options' is present while it should not
@Blacks-Army commented on GitHub (Aug 9, 2025):
In the Admin Diagnostics I only get this
API calls:
Header: 'x-xss-protection' does not contain '0'
2FA Connector calls:
Header: 'x-xss-protection' does not contain '0'
Header: 'x-frame-options' is present while it should not
Well in Vaultwarden I have multiple Keys. Why does it matter which I use I do not understand tbh.
PassKey - IOS Keychain
YubiKey 5C NFC (Main)
Bitwarden - in the vault itself
@Blacks-Army commented on GitHub (Aug 9, 2025):
Well in Vaultwarden I have multiple Keys. Why does it matter which I use I do not understand tbh.
PassKey - IOS Keychain
YubiKey 5C NFC (Main)
Bitwarden - in the vault itself
Sorry, it’s evening here in China and I’ve had a few beers. I’ll run the tests and add more logs tomorrow.
@IamTaoChen commented on GitHub (Aug 9, 2025):
Sorry, it’s evening here in China and I’ve had a few beers. I’ll run the tests and add more logs tomorrow.
I have some update about Client Actions (Mac Chrome Extension & Desktop App)
Logged out and attempted to log back in.
On the Chrome extension (Mac): Passkey is stored in Apple Passwords; fingerprint verification succeeded, but the next step did not proceed.
On the desktop app: No action occurred—no fingerprint prompt appeared.
Used an alternative 2FA method to log in to the desktop client.
Logged in again afterwards(extension), and it is now working.
I am not certain whether these actions are directly related to the this issue being discussed.
Sorry, this comment is using the latest image not testing(I forgot to mention it before)
@IamTaoChen commented on GitHub (Aug 9, 2025):
I have some update about Client Actions (Mac Chrome Extension & Desktop App)
1. Logged out and attempted to log back in.
- On the Chrome extension (Mac): Passkey is stored in Apple Passwords; fingerprint verification succeeded, but the next step did not proceed.
- On the desktop app: No action occurred—no fingerprint prompt appeared.
2. Used an alternative 2FA method to log in to the desktop client.
3. Logged in again afterwards(extension), and it is now working.
I am not certain whether these actions are directly related to the this issue being discussed.
----
Sorry, this comment is using the latest image not testing(I forgot to mention it before)
@BlackDex commented on GitHub (Aug 9, 2025):
@Blacks-Army, i would suggest to fix those items, as they might cause some weird issues with some clients.
I tried on a macOS device, and I'm able to add a key, but afterwards I'm unable to login with it.
```
[2025-08-09 19:15:42.416][webauthn_rs_core::core][DEBUG] extensions: AuthenticationSignedExtensions { unknown_keys: {} }
[2025-08-09 19:15:42.416][vaultwarden::api::core::two_factor::webauthn][ERROR] Credential not present
```
Using my YubiKey5c on the mac works fine though.
@IamTaoChen, if you are not using :latest, then it can't have anything to do with the latest webauthn-rs version, as that currently is only in the :testing tagged images.
I wouldn't be surprised if Apple doesn't fully adhere to the standards which might cause issues.
@BlackDex commented on GitHub (Aug 9, 2025):
@IamTaoChen, if you are not using `:latest`, then it can't have anything to do with the latest webauthn-rs version, as that currently is only in the `:testing` tagged images.
I wouldn't be surprised if Apple doesn't fully adhere to the standards which might cause issues.
However I noticed that this only happens on Software saved Passkeys like a Passkey saved in the Apple Keychain or in Bitwarden itself.
It works with YubiKey as a Passkey and Yubico OTP.
@Blacks-Army commented on GitHub (Aug 9, 2025):
Removing the Headers did not fix anything.
However I noticed that this only happens on Software saved Passkeys like a Passkey saved in the Apple Keychain or in Bitwarden itself.
It works with YubiKey as a Passkey and Yubico OTP.
[2025-08-10 01:31:39.022+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-10 01:31:39.023+0800][request][INFO] GET /api/devices/knowndevice
[2025-08-10 01:31:39.024+0800][response][INFO](get_known_device) GET /api/devices/knowndevice => 200 OK
[2025-08-10 01:31:39.025+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-10 01:31:39.059+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-10 01:31:39.060+0800][request][INFO] GET /icons/pw.kyzdt.com/icon.png
[2025-08-10 01:31:39.061+0800][reqwest::connect][DEBUG] starting new connection: https://xxxx.com/
[2025-08-10 01:31:39.062+0800][reqwest::connect][DEBUG] starting new connection: https://xxxx.com/
[2025-08-10 01:31:39.063+0800][vaultwarden::api::icons][WARN] IP xxx for domain 'xxxx' is not a global IP!
[2025-08-10 01:31:39.064+0800][response][INFO](icon_internal) GET /icons/<domain>/icon.png => 200 OK
[2025-08-10 01:31:39.064+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-10 01:31:42.191+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-10 01:31:42.193+0800][request][INFO] POST /identity/accounts/prelogin
[2025-08-10 01:31:42.194+0800][response][INFO](prelogin) POST /identity/accounts/prelogin => 200 OK
[2025-08-10 01:31:42.195+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-10 01:31:42.348+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-10 01:31:42.349+0800][request][INFO] POST /identity/connect/token
[2025-08-10 01:31:42.671+0800][error][ERROR] 2FA token not provided
[2025-08-10 01:31:42.671+0800][response][INFO](login) POST /identity/connect/token => 400 Bad Request
[2025-08-10 01:31:42.672+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-10 01:31:43.380+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-10 01:31:43.381+0800][request][INFO] GET /webauthn-connector.html?data=eyJhbGxvd0NyZWRlbnRpYWxzI
[2025-08-10 01:31:43.382+0800][response][INFO](web_files) GET /<p..> [10]=> 200 OK
[2025-08-10 01:31:43.383+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-10 01:31:49.245+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-10 01:31:49.246+0800][request][INFO] POST /identity/connect/token
[2025-08-10 01:31:49.563+0800][webauthn_rs_core::core][ERROR] Credential backup elligibility has changed!
[2025-08-10 01:31:49.563+0800][error][ERROR] Webauthn.
[CAUSE] CredentialBackupElligibilityInconsistent
[2025-08-10 01:31:49.564+0800][response][INFO](login) POST /identity/connect/token => 400 Bad Request
[2025-08-10 01:31:49.565+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
2FA by Authebticater APP
Wroks.
@IamTaoChen commented on GitHub (Aug 9, 2025):
## Testing Image
### Login with master password
#### 2FA by PassKey
1. Login from web
2. Input password and ask for the fingerprint
3. error happen
```bash
[2025-08-10 01:31:39.022+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-10 01:31:39.023+0800][request][INFO] GET /api/devices/knowndevice
[2025-08-10 01:31:39.024+0800][response][INFO] (get_known_device) GET /api/devices/knowndevice => 200 OK
[2025-08-10 01:31:39.025+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-10 01:31:39.059+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-10 01:31:39.060+0800][request][INFO] GET /icons/pw.kyzdt.com/icon.png
[2025-08-10 01:31:39.061+0800][reqwest::connect][DEBUG] starting new connection: https://xxxx.com/
[2025-08-10 01:31:39.062+0800][reqwest::connect][DEBUG] starting new connection: https://xxxx.com/
[2025-08-10 01:31:39.063+0800][vaultwarden::api::icons][WARN] IP xxx for domain 'xxxx' is not a global IP!
[2025-08-10 01:31:39.064+0800][response][INFO] (icon_internal) GET /icons/<domain>/icon.png => 200 OK
[2025-08-10 01:31:39.064+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-10 01:31:42.191+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-10 01:31:42.193+0800][request][INFO] POST /identity/accounts/prelogin
[2025-08-10 01:31:42.194+0800][response][INFO] (prelogin) POST /identity/accounts/prelogin => 200 OK
[2025-08-10 01:31:42.195+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-10 01:31:42.348+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-10 01:31:42.349+0800][request][INFO] POST /identity/connect/token
[2025-08-10 01:31:42.671+0800][error][ERROR] 2FA token not provided
[2025-08-10 01:31:42.671+0800][response][INFO] (login) POST /identity/connect/token => 400 Bad Request
[2025-08-10 01:31:42.672+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-10 01:31:43.380+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-10 01:31:43.381+0800][request][INFO] GET /webauthn-connector.html?data=eyJhbGxvd0NyZWRlbnRpYWxzI
[2025-08-10 01:31:43.382+0800][response][INFO] (web_files) GET /<p..> [10] => 200 OK
[2025-08-10 01:31:43.383+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-10 01:31:49.245+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-10 01:31:49.246+0800][request][INFO] POST /identity/connect/token
[2025-08-10 01:31:49.563+0800][webauthn_rs_core::core][ERROR] Credential backup elligibility has changed!
[2025-08-10 01:31:49.563+0800][error][ERROR] Webauthn.
[CAUSE] CredentialBackupElligibilityInconsistent
[2025-08-10 01:31:49.564+0800][response][INFO] (login) POST /identity/connect/token => 400 Bad Request
[2025-08-10 01:31:49.565+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
```
<img width="1454" height="1140" alt="Image" src="https://github.com/user-attachments/assets/f4f1d1f8-62f5-440d-b2e4-f09f572bbe68" />
#### 2FA by Authebticater APP
Wroks.
@BlackDex Hi, I have noticed that the diagnostics page(Support String) displays certain OIDC information that is not concealed.(not about this issue, but maybe important)
@IamTaoChen commented on GitHub (Aug 9, 2025):
@BlackDex Hi, I have noticed that the diagnostics page(Support String) displays certain OIDC information that is not concealed.(not about this issue, but maybe important)
@BlackDex Hi, I have noticed that the diagnostics page(Support String) displays certain OIDC information that is not concealed.(not about this issue, but maybe important)
@Timshel
@BlackDex commented on GitHub (Aug 9, 2025):
> [@BlackDex](https://github.com/BlackDex) Hi, I have noticed that the diagnostics page(Support String) displays certain OIDC information that is not concealed.(not about this issue, but maybe important)
@Timshel
@stefan0xC commented on GitHub (Aug 9, 2025):
I think we should be masking (at least) `sso_client_id` and `sso_authority` here https://github.com/dani-garcia/vaultwarden/blob/a133d4e90c6f864c87ad54a877ea501f4d4f92ec/src/config.rs#L268-L272
pub fn get_support_json(&self) -> serde_json::Value {
// Define which config keys need to be masked.
// Pass types will always be masked and no need to put them in the list.
// Besides Pass, only String types will be masked via _privacy_mask.
const PRIVACY_CONFIG: &[&str] = &[
and sso_callback_path which contains the domain of vaultwarden
@IamTaoChen commented on GitHub (Aug 9, 2025):
> I think we should be masking (at least) `sso_client_id` and `sso_authority` here
>
> [vaultwarden/src/config.rs](https://github.com/dani-garcia/vaultwarden/blob/a133d4e90c6f864c87ad54a877ea501f4d4f92ec/src/config.rs#L268-L272)
>
> Lines 268 to 272 in [a133d4e](/dani-garcia/vaultwarden/commit/a133d4e90c6f864c87ad54a877ea501f4d4f92ec)
>
> pub fn get_support_json(&self) -> serde_json::Value {
> // Define which config keys need to be masked.
> // Pass types will always be masked and no need to put them in the list.
> // Besides Pass, only String types will be masked via _privacy_mask.
> const PRIVACY_CONFIG: &[&str] = &[
and `sso_callback_path` which contains the domain of vaultwarden
@Blacks-Army commented on GitHub (Aug 9, 2025):
> I think we should be masking (at least) `sso_client_id` and `sso_authority` here https://github.com/dani-garcia/vaultwarden/blob/a133d4e90c6f864c87ad54a877ea501f4d4f92ec/src/config.rs#L268-L272
You mean the secret right?
// Pass types will always be masked and no need to put them in the list.
You mean the secret right?
Nope. That already is of type Pass so it's hidden already. I've updated my PR #6153 to also mask those three options.
@stefan0xC commented on GitHub (Aug 9, 2025):
> > // Pass types will always be masked and no need to put them in the list.
> You mean the secret right?
Nope. That already is of type `Pass` so it's hidden already. I've updated my PR #6153 to also mask those three options.
When debugging an issue the sso_authority can be useful, but it can be sensitive too.
The sso_client_id will almost certainly never be useful.
@Timshel commented on GitHub (Aug 9, 2025):
When debugging an issue the `sso_authority` can be useful, but it can be sensitive too.
The `sso_client_id` will almost certainly never be useful.
start_passkey_authentication()allowsallow_backup_eligible_upgrade while start_securitykey_authenticationdisallowsallow_backup_eligible_upgrade.
Unfortunately I don't know what security implications this has. Originally start_passkey_authentication() was used but it required modifying the JSON, now start_securitykey_authentication() is used since it produces the same JSON webauthn request as before the crate update, but as it seems, it doesn't allow allow_backup_eligible_upgrade.
In the CredentialV3 this wasn't present, in the migration, a value of false is assumed for backup_eligible.
I think allowing allow_backup_eligible_upgrade using the underlying webauthn_core_rs crate or using start_passkey_authentication() as before, could fix this.
Since you already pinged two Maintainers of webauthn-rs, maybe they know what security implication allow_backup_eligible_upgrade has and why this is allowed for start_passkey_authentication() but not start_securitykey_authentication()
The docs mention the following regarding the *_security_key() methods:
A Security Key for a user. These are the legacy “second factor” method of security tokens.
You should avoid this type in favour of Passkey or AttestedPasskey
Maybe this is the reason that security keys work but passkeys don't always?
Contrary to the initial suggestion, using start_passkey_authentication() may be more compatible with the original implementation with webauthn-rs 0.3
@zUnixorn commented on GitHub (Aug 9, 2025):
> I't might be as easy as allowing `allow_backup_eligible_upgrade` but I'm not sure.
Looking at the code ([here](https://github.com/kanidm/webauthn-rs/blob/fee572215b7a6906b41f271b94ad4a8929bc5d38/webauthn-rs-core/src/core.rs#L887)), this seems to be the problem, I am not sure what this means though.
The value of `allow_backup_eligible_upgrade` seems to be a difference between [start_securitykey_authentication()](https://docs.rs/webauthn-rs/latest/webauthn_rs/struct.Webauthn.html#method.start_securitykey_authentication) and [start_passkey_authentication()](https://docs.rs/webauthn-rs/latest/webauthn_rs/struct.Webauthn.html#method.start_passkey_authentication).
`start_passkey_authentication()` [allows](https://docs.rs/webauthn-rs/latest/src/webauthn_rs/lib.rs.html#682) `allow_backup_eligible_upgrade` while `start_securitykey_authentication` [disallows](https://docs.rs/webauthn-rs/latest/src/webauthn_rs/lib.rs.html#961) `allow_backup_eligible_upgrade`.
Unfortunately I don't know what security implications this has. Originally `start_passkey_authentication()` was used but it required modifying the JSON, now `start_securitykey_authentication()` is used since it produces the same JSON webauthn request as before the crate update, but as it seems, it doesn't allow `allow_backup_eligible_upgrade`.
In the [CredentialV3](https://docs.rs/webauthn-rs-core/0.5.2/webauthn_rs_core/proto/struct.CredentialV3.html) this wasn't present, in the [migration](https://docs.rs/webauthn-rs-core/0.5.2/src/webauthn_rs_core/interface.rs.html#320-346), a value of `false` is assumed for `backup_eligible`.
I think allowing `allow_backup_eligible_upgrade` using the underlying webauthn_core_rs crate or using `start_passkey_authentication()` as before, could fix this.
Since you already pinged two Maintainers of webauthn-rs, maybe they know what security implication `allow_backup_eligible_upgrade` has and why this is allowed for `start_passkey_authentication()` but not `start_securitykey_authentication()`
The [docs mention](https://docs.rs/webauthn-rs/latest/webauthn_rs/prelude/struct.SecurityKey.html) the following regarding the *_security_key() methods:
> A Security Key for a user. These are the legacy “second factor” method of security tokens.
> You should avoid this type in favour of [Passkey](https://docs.rs/webauthn-rs/latest/webauthn_rs/prelude/struct.Passkey.html) or [AttestedPasskey](https://docs.rs/webauthn-rs/latest/webauthn_rs/prelude/struct.AttestedPasskey.html)
Maybe this is the reason that security keys work but passkeys don't always?
Contrary to the [initial suggestion](https://github.com/dani-garcia/vaultwarden/pull/5934#pullrequestreview-3058117864), using `start_passkey_authentication()` may be more compatible with the original implementation with webauthn-rs 0.3
I couldn't reproduce this issue on my end and cannot test this, but you could try if the version in my branch exhibits the same behavior or if it actually fixes the problem.
@zUnixorn commented on GitHub (Aug 9, 2025):
I reverted to using the original start_passkey_authentication() approach in this branch: https://github.com/zUnixorn/vaultwarden/tree/use_passkey_methods_webauthn_rs
I couldn't reproduce this issue on my end and cannot test this, but you could try if the version in my branch exhibits the same behavior or if it actually fixes the problem.
Since you already pinged two Maintainers of webauthn-rs, maybe they know what security implication allow_backup_eligible_upgrade has and why this is allowed for start_passkey_authentication() but not start_securitykey_authentication()
The difference is that it allows a key that was previously hardware bound to a single device, to now be transient to many devices. As an RP, this changes the security assumptions of the device, since what you thought was single device, now has moved.
In general for the vaultwarden use case, you probably want "securitykey" options here, because that treats the device as a single factor, where as passkeys treat the device as self-contained multifactor and will force user verification.
@Firstyear commented on GitHub (Aug 10, 2025):
> Since you already pinged two Maintainers of webauthn-rs, maybe they know what security implication `allow_backup_eligible_upgrade` has and why this is allowed for `start_passkey_authentication()` but not `start_securitykey_authentication()`
The difference is that it allows a key that was previously hardware bound to a single device, to now be transient to many devices. As an RP, this changes the security assumptions of the device, since what you thought was single device, now has moved.
In general for the vaultwarden use case, you probably want "securitykey" options here, because that treats the device as a single factor, where as passkeys treat the device as self-contained multifactor and will force user verification.
Since you already pinged two Maintainers of webauthn-rs, maybe they know what security implication allow_backup_eligible_upgrade has and why this is allowed for start_passkey_authentication() but not start_securitykey_authentication()
The difference is that it allows a key that was previously hardware bound to a single device, to now be transient to many devices. As an RP, this changes the security assumptions of the device, since what you thought was single device, now has moved.
In general for the vaultwarden use case, you probably want "securitykey" options here, because that treats the device as a single factor, where as passkeys treat the device as self-contained multifactor and will force user verification.
I don't know if I quite understand that, but does that mean we need to use start_passkey_authentication() for soft tokens (passkeys in password managers) to work?
@Blacks-Army commented on GitHub (Aug 10, 2025):
> > Since you already pinged two Maintainers of webauthn-rs, maybe they know what security implication `allow_backup_eligible_upgrade` has and why this is allowed for `start_passkey_authentication()` but not `start_securitykey_authentication()`
>
> The difference is that it allows a key that was previously hardware bound to a single device, to now be transient to many devices. As an RP, this changes the security assumptions of the device, since what you thought was single device, now has moved.
>
> In general for the vaultwarden use case, you probably want "securitykey" options here, because that treats the device as a single factor, where as passkeys treat the device as self-contained multifactor and will force user verification.
I don't know if I quite understand that, but does that mean we need to use `start_passkey_authentication()` for soft tokens (passkeys in password managers) to work?
I'm not sure what v0.3.2 was using as it's base verification policy, which we should try to not change i think.
But Bitwarden did renamed the MFA option from WebauthN/FIDO to Passkeys. While I'm of course for more security, it might make it more difficult for users to use the same WebauthN on both there iOS device and macOS. Or maybe an Android device which was transferred, of that modifies something maybe, or possibly at all.
I might check what the different outcome will be between the two options.
@BlackDex commented on GitHub (Aug 10, 2025):
I'm not sure what v0.3.2 was using as it's base verification policy, which we should try to not change i think.
But Bitwarden did renamed the MFA option from WebauthN/FIDO to Passkeys. While I'm of course for more security, it might make it more difficult for users to use the same WebauthN on both there iOS device and macOS. Or maybe an Android device which was transferred, of that modifies something maybe, or possibly at all.
I might check what the different outcome will be between the two options.
I couldn't reproduce this issue on my end and cannot test this, but you could try if the version in my branch exhibits the same behavior or if it actually fixes the problem.
Is there an image available? Or do I need to build it myself? If so I don't know how...
@Blacks-Army commented on GitHub (Aug 10, 2025):
> I reverted to using the original start_passkey_authentication() approach in this branch: https://github.com/zUnixorn/vaultwarden/tree/use_passkey_methods_webauthn_rs
>
> I couldn't reproduce this issue on my end and cannot test this, but you could try if the version in my branch exhibits the same behavior or if it actually fixes the problem.
Is there an image available? Or do I need to build it myself? If so I don't know how...
Is there an image available? Or do I need to build it myself? If so I don't know how...
Currently there is no image available, but I can build one later so it's easier to test. I will comment it here, when i've come around to make one available.
@zUnixorn commented on GitHub (Aug 10, 2025):
> Is there an image available? Or do I need to build it myself? If so I don't know how...
Currently there is no image available, but I can build one later so it's easier to test. I will comment it here, when i've come around to make one available.
I tried with the patch form @zUnixorn, but that doesn't solve the Apple macOS/iOS issue for me.
Either it's not fully supported by webauthn-rs, or it's not using standards.
Unfortunately the test/demo application isn't working of webauthn-rs, which prevents me, and others from seeing which data is transfered/used to try and authenticate. This might help the developers of webauthn-rs to fix issues or maybe supply some details regarding issues with some of these devices.
@BlackDex commented on GitHub (Aug 10, 2025):
I tried with the patch form @zUnixorn, but that doesn't solve the Apple macOS/iOS issue for me.
Either it's not fully supported by webauthn-rs, or it's not using standards.
Unfortunately the test/demo application isn't working of webauthn-rs, which prevents me, and others from seeing which data is transfered/used to try and authenticate. This might help the developers of webauthn-rs to fix issues or maybe supply some details regarding issues with some of these devices.
@Blacks-Army commented on GitHub (Aug 10, 2025):
Kanidm uses also webauthn-rs 0.5.2 and there it does work
https://github.com/kanidm/kanidm/blob/38b54969d1e3e5ca9aeb6189fec49e43577d5652/Cargo.toml#L305C1-L305C69
@Blacks-Army commented on GitHub (Aug 10, 2025):
I am just seeing this part maybe we need this dependency?
https://github.com/kanidm/kanidm/blob/38b54969d1e3e5ca9aeb6189fec49e43577d5652/Cargo.toml#L302
@Blacks-Army, that seems to be the client-side of the code, not server-side, and thus not something useful.
I do think i see some strange things though. Currently debugging to see what the difference are.
@BlackDex commented on GitHub (Aug 10, 2025):
@Blacks-Army, that seems to be the client-side of the code, not server-side, and thus not something useful.
I do think i see some strange things though. Currently debugging to see what the difference are.
There is a check if the credential id's match, but, it also checks if there is an updated needed, but if that part fails, the whole authentication fails. And, if i read the documentation correctly, almost no device uses this feature, and it's also not really needed.
Ill check what happens if i update this. But, that still leaves me a bit undecided if using security or passkey would be the right way to go. But, since we probably want to have Soft tokens like Apple work cross device, and that needs to allow the backup_state to be able to be set to true, I think passkeys is probably the way to go.
@BlackDex commented on GitHub (Aug 10, 2025):
Yes, I have found a bug which prevents almost every device except for some hardware keys to continue.
Here:
https://github.com/dani-garcia/vaultwarden/blob/e35c6f87054563af6ecfec9f100779523b62c473/src/api/core/two_factor/webauthn.rs#L437
There is a check if the credential id's match, but, it also checks if there is an updated needed, but if that part fails, the whole authentication fails. And, if i read the documentation correctly, almost no device uses this feature, and it's also not really needed.
Ill check what happens if i update this. But, that still leaves me a bit undecided if using `security` or `passkey` would be the right way to go. But, since we probably want to have Soft tokens like Apple work cross device, and that needs to allow the `backup_state` to be able to be set to `true`, I think `passkeys` is probably the way to go.
@BlackDex commented on GitHub (Aug 10, 2025):
Once https://github.com/dani-garcia/vaultwarden/actions/runs/16864227130 is finished you should be able to test if this fully resolves the issues reported here by using the `:testing` tagged images.
[2025-08-11 01:36:01.910+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:01.911+0800][request][INFO] GET /icons/xxxx.com/icon.png
[2025-08-11 01:36:01.912+0800][opendal::services::fs::backend][DEBUG] backend build started: FsBuilder { config: FsConfig { root: Some("data/icon_cache"), atomic_write_dir: None }}[2025-08-11 01:36:01.912+0800][opendal::services::fs::backend][DEBUG] backend use root data/icon_cache
[2025-08-11 01:36:02.062+0800][reqwest::connect][DEBUG] starting new connection: https://xxx.com/
[2025-08-11 01:36:02.062+0800][reqwest::connect][DEBUG] starting new connection: https://xxxx.com/
[2025-08-11 01:36:02.064+0800][vaultwarden::api::icons][WARN] IP 172.30.32.253 for domain 'xxxx.com' is not a global IP!
[2025-08-11 01:36:02.064+0800][response][INFO](icon_internal) GET /icons/<domain>/icon.png => 200 OK
[2025-08-11 01:36:02.064+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-11 01:36:04.153+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:04.154+0800][request][INFO] GET /api/devices/knowndevice
[2025-08-11 01:36:04.155+0800][response][INFO](get_known_device) GET /api/devices/knowndevice => 200 OK
[2025-08-11 01:36:04.156+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-11 01:36:05.094+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:05.095+0800][request][INFO] POST /identity/accounts/prelogin
[2025-08-11 01:36:05.096+0800][response][INFO](prelogin) POST /identity/accounts/prelogin => 200 OK
[2025-08-11 01:36:05.097+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-11 01:36:05.231+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:05.232+0800][request][INFO] POST /identity/connect/token
[2025-08-11 01:36:05.567+0800][error][ERROR] 2FA token not provided
[2025-08-11 01:36:05.567+0800][response][INFO](login) POST /identity/connect/token => 400 Bad Request
[2025-08-11 01:36:05.569+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-11 01:36:06.154+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:06.155+0800][request][INFO] GET /webauthn-connector.html?data=eyJhbGxvd0NyZWRlbnRpYWxzI
[2025-08-11 01:36:06.156+0800][response][INFO](web_files) GET /<p..> [10]=> 200 OK
[2025-08-11 01:36:06.157+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-11 01:36:06.214+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:06.214+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:06.216+0800][request][INFO] GET /connectors/webauthn.b8f01de2a110dcccf490.js
[2025-08-11 01:36:06.217+0800][request][INFO] GET /connectors/webauthn.6c71ed707506614311d1.css
[2025-08-11 01:36:06.217+0800][response][INFO](web_files) GET /<p..> [10]=> 200 OK
[2025-08-11 01:36:06.218+0800][response][INFO](web_files) GET /<p..> [10]=> 200 OK
[2025-08-11 01:36:06.218+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-11 01:36:06.221+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-11 01:36:09.993+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:09.993+0800][rustls::server::hs][DEBUG] Chosen ALPN protocol [xxxx][2025-08-11 01:36:09.998+0800][request][INFO] GET /alive
[2025-08-11 01:36:09.998+0800][response][INFO](alive) GET /alive => 200 OK
[2025-08-11 01:36:09.999+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-11 01:36:10.462+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:10.463+0800][request][INFO] POST /identity/connect/token
[2025-08-11 01:36:10.787+0800][webauthn_rs_core::core][ERROR] Credential backup elligibility has changed!
[2025-08-11 01:36:10.787+0800][error][ERROR] Webauthn.
[CAUSE] CredentialBackupElligibilityInconsistent
[2025-08-11 01:36:10.787+0800][response][INFO](login) POST /identity/connect/token => 400 Bad Request
[2025-08-11 01:36:10.789+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
@IamTaoChen commented on GitHub (Aug 10, 2025):
No, It still doesn't work
```bash
[2025-08-11 01:36:01.910+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:01.911+0800][request][INFO] GET /icons/xxxx.com/icon.png
[2025-08-11 01:36:01.912+0800][opendal::services::fs::backend][DEBUG] backend build started: FsBuilder { config: FsConfig { root: Some("data/icon_cache"), atomic_write_dir: None } }
[2025-08-11 01:36:01.912+0800][opendal::services::fs::backend][DEBUG] backend use root data/icon_cache
[2025-08-11 01:36:02.062+0800][reqwest::connect][DEBUG] starting new connection: https://xxx.com/
[2025-08-11 01:36:02.062+0800][reqwest::connect][DEBUG] starting new connection: https://xxxx.com/
[2025-08-11 01:36:02.064+0800][vaultwarden::api::icons][WARN] IP 172.30.32.253 for domain 'xxxx.com' is not a global IP!
[2025-08-11 01:36:02.064+0800][response][INFO] (icon_internal) GET /icons/<domain>/icon.png => 200 OK
[2025-08-11 01:36:02.064+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-11 01:36:04.153+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:04.154+0800][request][INFO] GET /api/devices/knowndevice
[2025-08-11 01:36:04.155+0800][response][INFO] (get_known_device) GET /api/devices/knowndevice => 200 OK
[2025-08-11 01:36:04.156+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-11 01:36:05.094+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:05.095+0800][request][INFO] POST /identity/accounts/prelogin
[2025-08-11 01:36:05.096+0800][response][INFO] (prelogin) POST /identity/accounts/prelogin => 200 OK
[2025-08-11 01:36:05.097+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-11 01:36:05.231+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:05.232+0800][request][INFO] POST /identity/connect/token
[2025-08-11 01:36:05.567+0800][error][ERROR] 2FA token not provided
[2025-08-11 01:36:05.567+0800][response][INFO] (login) POST /identity/connect/token => 400 Bad Request
[2025-08-11 01:36:05.569+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-11 01:36:06.154+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:06.155+0800][request][INFO] GET /webauthn-connector.html?data=eyJhbGxvd0NyZWRlbnRpYWxzI
[2025-08-11 01:36:06.156+0800][response][INFO] (web_files) GET /<p..> [10] => 200 OK
[2025-08-11 01:36:06.157+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-11 01:36:06.214+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:06.214+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:06.216+0800][request][INFO] GET /connectors/webauthn.b8f01de2a110dcccf490.js
[2025-08-11 01:36:06.217+0800][request][INFO] GET /connectors/webauthn.6c71ed707506614311d1.css
[2025-08-11 01:36:06.217+0800][response][INFO] (web_files) GET /<p..> [10] => 200 OK
[2025-08-11 01:36:06.218+0800][response][INFO] (web_files) GET /<p..> [10] => 200 OK
[2025-08-11 01:36:06.218+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-11 01:36:06.221+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-11 01:36:09.993+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:09.993+0800][rustls::server::hs][DEBUG] Chosen ALPN protocol [xxxx]
[2025-08-11 01:36:09.998+0800][request][INFO] GET /alive
[2025-08-11 01:36:09.998+0800][response][INFO] (alive) GET /alive => 200 OK
[2025-08-11 01:36:09.999+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
[2025-08-11 01:36:10.462+0800][rustls::server::hs][DEBUG] decided upon suite TLS13_AES_256_GCM_SHA384
[2025-08-11 01:36:10.463+0800][request][INFO] POST /identity/connect/token
[2025-08-11 01:36:10.787+0800][webauthn_rs_core::core][ERROR] Credential backup elligibility has changed!
[2025-08-11 01:36:10.787+0800][error][ERROR] Webauthn.
[CAUSE] CredentialBackupElligibilityInconsistent
[2025-08-11 01:36:10.787+0800][response][INFO] (login) POST /identity/connect/token => 400 Bad Request
[2025-08-11 01:36:10.789+0800][rustls::common_state][DEBUG] Sending warning alert CloseNotify
```
And you both are sure you did a docker pull or docker compose pull first?
What is the version running? Does it has the last commit tag currently in our main branch?
Also @Blacks-Army, do you still have those strange header errors? And did you fixed those warnings the diagnostics page reported?
@BlackDex commented on GitHub (Aug 10, 2025):
And you both are sure you did a `docker pull` or `docker compose pull` first?
What is the version running? Does it has the last commit tag currently in our main branch?
Also @Blacks-Army, do you still have those strange header errors? And did you fixed those warnings the diagnostics page reported?
Also, i think that the issue regarding @IamTaoChen still is because of the backup key is being used in some way. And not the original key used, but I'm not 100% sure regarding that.
Then maybe @zUnixorn could rebase on main, and provide a working image maybe of his security > passkey changes?
That is then the only item i think could be the issue then.
But, at least Bitwarden Passkeys and macOS/iOS Passkeys work again without issues.
@BlackDex commented on GitHub (Aug 10, 2025):
Also, i think that the issue regarding @IamTaoChen still is because of the backup key is being used in some way. And not the original key used, but I'm not 100% sure regarding that.
Then maybe @zUnixorn could rebase on main, and provide a working image maybe of his security > passkey changes?
That is then the only item i think could be the issue then.
But, at least Bitwarden Passkeys and macOS/iOS Passkeys work again without issues.
Also, i think that the issue regarding @IamTaoChen still is because of the backup key is being used in some way. And not the original key used, but I'm not 100% sure regarding that.
Then maybe @zUnixorn could rebase on main, and provide a working image maybe of his security > passkey changes?
That is then the only item i think could be the issue then.
But, at least Bitwarden Passkeys and macOS/iOS Passkeys work again without issues.
For me iOS and Bitwarden Passkeys didn't work... did you maybe add them on a freshly installed instance? I am doing a Upgrade and want to use my already saved passkeys.
@Blacks-Army commented on GitHub (Aug 10, 2025):
> Also, i think that the issue regarding @IamTaoChen still is because of the backup key is being used in some way. And not the original key used, but I'm not 100% sure regarding that.
>
> Then maybe @zUnixorn could rebase on main, and provide a working image maybe of his security > passkey changes?
> That is then the only item i think could be the issue then.
>
> But, at least Bitwarden Passkeys and macOS/iOS Passkeys work again without issues.
For me iOS and Bitwarden Passkeys didn't work... did you maybe add them on a freshly installed instance? I am doing a Upgrade and want to use my already saved passkeys.
@zUnixorn commented on GitHub (Aug 11, 2025):
Ok, I rebased and generated the docker images with the `*_passkey_*` methods based on alpine or debian:
* `docker pull ghcr.io/zunixorn/vaultwarden:testing-alpine`
* `docker pull ghcr.io/zunixorn/vaultwarden:testing`
Does the problem persist with these versions?
Now it does not fail with CredentialBackupElligibilityInconsistent but instead with CredentialMayNotBeHardwareBound so switching to the passkey methods did indeed fix that previous error. Since your passkey seems to have the backup_state set to true, but the migration defaults to false for backup_eligible, the webauthn crate doesn't allow you to verify with that passkey.
I updated the docker image to migrate the passkeys with backup_eligible explicitly set to true, I don't know if that's something that should be done, but if this fixes the problem, we at least know where the problem lies.
So if you update the image, the CredentialMayNotBeHardwareBound error hopefully goes away too.
@zUnixorn commented on GitHub (Aug 11, 2025):
Now it does not fail with `CredentialBackupElligibilityInconsistent` but instead with `CredentialMayNotBeHardwareBound` so switching to the passkey methods did indeed fix that previous error. Since your passkey seems to have the `backup_state` set to `true`, but the migration [defaults](https://docs.rs/webauthn-rs-core/latest/src/webauthn_rs_core/interface.rs.html#336) to `false` for `backup_eligible`, the webauthn crate doesn't allow you to verify with that passkey.
I updated the docker image to migrate the passkeys with `backup_eligible` explicitly set to `true`, I don't know if that's something that should be done, but if this fixes the problem, we at least know where the problem lies.
So if you update the image, the `CredentialMayNotBeHardwareBound` error hopefully goes away too.
Thank you for testing this! Maybe this fixed @IamTaoChen's problem as well.
Now the question is, if it is desired to always migrate the old passkeys with backup_eligible set to true and using the *_passkey_* methods instead of *_securitykey_*. Since the webauthn 0.3 version didn't even check nor track this, I would think that allowing this wouldn't make this less secure then the v0.3 implementation, but I am not sure if this assumption is actually correct.
@zUnixorn commented on GitHub (Aug 11, 2025):
Thank you for testing this! Maybe this fixed @IamTaoChen's problem as well.
~~Now the question is, if it is desired to always migrate the old passkeys with `backup_eligible` set to `true` and using the `*_passkey_*` methods instead of `*_securitykey_*`. Since the webauthn 0.3 version didn't even check nor track this, I would think that allowing this wouldn't make this less secure then the v0.3 implementation, but I am not sure if this assumption is actually correct.~~
I would say, use passkeys, as that is also what Bitwarden calls it these days in the Security Settings of the user.
It would also help implementing the actual passkey login feature i guess, to not have multiple types of webauthn formats.
And, indeed, to stay compatible with the previous versions, i would say switch to *_passkey_*. If you can create a PR for that, then Ill do some tests my self too, and if ok, ill merge :).
@BlackDex commented on GitHub (Aug 11, 2025):
I would say, use passkeys, as that is also what Bitwarden calls it these days in the Security Settings of the user.
It would also help implementing the actual passkey login feature i guess, to not have multiple types of webauthn formats.
And, indeed, to stay compatible with the previous versions, i would say switch to `*_passkey_*`. If you can create a PR for that, then Ill do some tests my self too, and if ok, ill merge :).
I will create a PR for that later today. Should I also change the backup_eligible value (this field) like I did in my current branch or leave it and only change to the passkey methods?
@zUnixorn commented on GitHub (Aug 11, 2025):
I will create a PR for that later today. ~~Should I also change the `backup_eligible` value ([this field](https://docs.rs/webauthn-rs-core/0.5.2/webauthn_rs_core/proto/struct.CredentialV5.html#structfield.backup_eligible)) like I did in my current branch or leave it and only change to the passkey methods?~~
If I'm correct leaving that off and switch to passkeys should solve it right? Since passkeys has that enabled by default?
@BlackDex commented on GitHub (Aug 11, 2025):
If I'm correct leaving that off and switch to passkeys should solve it right? Since passkeys has that enabled by default?
Ok after thinking about it, switching to passkeys should be enough. When I only applied that change, there was still CredentialMayNotBeHardwareBound thrown as an error. But after looking through the webauthn-rs code, this shouldn't happen if update_credential() is called, since it should update backup_eligible which would prevent CredentialMayNotBeHardwareBound from being thrown.
@Blacks-Army when you tested the first change that didn't work, did you try to authenticate multiple times or just once? I can't test it myself but I would think that it should only fail the first time but update backup_eligible. The second time it should work, because the value is set correctly then.
@zUnixorn commented on GitHub (Aug 11, 2025):
Ok after thinking about it, switching to passkeys should be enough. When I only applied that change, there was still `CredentialMayNotBeHardwareBound` thrown as an error. But after looking through the webauthn-rs code, this shouldn't happen if `update_credential()` is called, since it should update `backup_eligible` which would prevent `CredentialMayNotBeHardwareBound` from being thrown.
@Blacks-Army when you tested the first change that didn't work, did you try to authenticate multiple times or just once? I can't test it myself but I would think that it should only fail the first time but update `backup_eligible`. The second time it should work, because the value is set correctly then.
And then try to authenticate 2 times, the first time should fail but update the stored backup eligibility and the second time should hopefully work then.
@zUnixorn commented on GitHub (Aug 11, 2025):
Ok, could you try this image again:
* `docker pull ghcr.io/zunixorn/vaultwarden@sha256:c6a9c574220ad650e7ee321366d3216b47d848651d2d5cb5ebf5ba674f5f4535`
And then try to authenticate 2 times, the first time should fail but update the stored backup eligibility and the second time should hopefully work then.
If that's not the case the Credential doesn't get updated properly and then that should be fixed instead.
@zUnixorn commented on GitHub (Aug 11, 2025):
If that's not the case the Credential doesn't get updated properly and then that should be fixed instead.
Hmm.. I'm not sure we should set it for all items as you currently do @zUnixorn.
My YubiKey for example has that set to false, which i would like to keep it that way. As that is a physical token and not software.
The *_passkey_* functions allow that boolean to be true. But, it doesn't force it to be true.
Setting everything to true, could cause a security issue i think.
@BlackDex commented on GitHub (Aug 11, 2025):
Hmm.. I'm not sure we should set it for all items as you currently do @zUnixorn.
My YubiKey for example has that set to false, which i would like to keep it that way. As that is a physical token and not software.
The `*_passkey_*` functions allow that boolean to be true. But, it doesn't force it to be true.
Setting everything to true, could cause a security issue i think.
Yeah, that's not the correct way. But I think I know what the problem is now.
When allow_backup_eligible_upgrade the backup_eligible should be updated when update_credential() is called. This works when the backup_state is still false e.g. the Passkey wasn't backed up. Because in that case the Authentication doesn't throw an error which allows update_credential() to be called and since backup_eligible is now set to the correct value, future authentications even after the key was backed up e.g. backup_state is true, will work fine.
But if the key was already backed up before the migration, backup_state is already true, which fails the Authentication and therefore prevents updating the Credential, which would actually solve the problem.
I am not sure if this is intended or a bug, but I am thinking about opening an issue in webauthn-rs since migrating already backed up passkeys seems to be impossible with my current understanding of its implementation.
But authenticate_credential() throws an error here if backup_state is true when backup_eligible wasn't updated, regardless of allow_backup_eligible_upgrade's value.
@zUnixorn commented on GitHub (Aug 11, 2025):
Yeah, that's not the correct way. But I think I know what the problem is now.
When `allow_backup_eligible_upgrade` the `backup_eligible` should be updated when `update_credential()` is called. This works when the `backup_state` is still `false` e.g. the Passkey wasn't backed up. Because in that case the Authentication doesn't throw an error which allows `update_credential()` to be called and since `backup_eligible` is now set to the correct value, future authentications even after the key was backed up e.g. `backup_state` is `true`, will work fine.
But if the key was already backed up before the migration, `backup_state` is already true, which fails the Authentication and therefore prevents updating the Credential, which would actually solve the problem.
I am not sure if this is intended or a bug, but I am thinking about opening an issue in webauthn-rs since migrating already backed up passkeys seems to be impossible with my current understanding of its implementation.
For Reference:
* [finish_passkey_authentication()](https://docs.rs/webauthn-rs/0.5.2/src/webauthn_rs/lib.rs.html#717-723) calls the internal [authenticate_credential()](https://docs.rs/webauthn-rs/0.5.2/src/webauthn_rs/lib.rs.html#717-723) method.
* But [authenticate_credential()](https://docs.rs/webauthn-rs/0.5.2/src/webauthn_rs/lib.rs.html#717-723) throws an error [here](https://docs.rs/webauthn-rs-core/latest/src/webauthn_rs_core/core.rs.html#893) if `backup_state` is true when `backup_eligible` wasn't updated, regardless of `allow_backup_eligible_upgrade`'s value.
* But to update `backup_eligible`, [finish_passkey_authentication()](https://docs.rs/webauthn-rs/0.5.2/src/webauthn_rs/lib.rs.html#717-723) must succeed to get the updated [AuthenticationResult](https://docs.rs/webauthn-rs/latest/webauthn_rs/prelude/struct.AuthenticationResult.html) and use it to update the credential's `backup_eligible` with [update_credential()](https://docs.rs/webauthn-rs/0.5.2/webauthn_rs/prelude/struct.Passkey.html#method.update_credential)
The problem here is, that my Hardware token, a YubiKey5c, has it set to false. You arn't allowed to set true to false for this flag. From false to true is allowed.
Using your latest patched version breaks my YubiKey login, but all the others are working now.
The main problem here is the migration. It migrates all items without knowing the correct values.
The only way i see to fix this, is migrate per credential upon usage, and extract the correct values from the provided V5 layout.
@BlackDex commented on GitHub (Aug 11, 2025):
The problem here is, that my Hardware token, a YubiKey5c, has it set to false. You arn't allowed to set `true` to `false` for this flag. From `false` to `true` is allowed.
Using your latest patched version breaks my YubiKey login, but all the others are working now.
The main problem here is the migration. It migrates all items without knowing the correct values.
The only way i see to fix this, is migrate per credential upon usage, and extract the correct values from the provided V5 layout.
I opened https://github.com/kanidm/webauthn-rs/issues/510, maybe I missed something but I am not sure how CredentialV3 is supposed to be migrated to CredentialV5 when the Passkey was backed up previously
@zUnixorn commented on GitHub (Aug 11, 2025):
I opened https://github.com/kanidm/webauthn-rs/issues/510, maybe I missed something but I am not sure how CredentialV3 is supposed to be migrated to CredentialV5 when the Passkey was backed up previously
The problem here is, that my Hardware token, a YubiKey5c, has it set to false. You arn't allowed to set true to false for this flag. From false to true is allowed.
Using your latest patched version breaks my YubiKey login, but all the others are working now. The main problem here is the migration. It migrates all items without knowing the correct values.
Yes, that's my understanding as well.
The only way i see to fix this, is migrate per credential upon usage, and extract the correct values from the provided V5 layout.
Since the authenticator tells us what it expects to be the correct values, they could be set before authenticating to the correct ones, which would make this work correctly.
Normally this would be done automatically here, but because of the the "invalid state", it does not.
Unfortunately, manually setting the backup_eligible to the one provided by the authenticator upon first authentication would be quite hard since there would be some parsing to do without the help of the webauthn-rs crate. So this should ideally be possible to do in webauthn-rs itself for the purpose of migrating to the more extensive CredentialV5.
@zUnixorn commented on GitHub (Aug 11, 2025):
> The problem here is, that my Hardware token, a YubiKey5c, has it set to false. You arn't allowed to set `true` to `false` for this flag. From `false` to `true` is allowed.
>
> Using your latest patched version breaks my YubiKey login, but all the others are working now. The main problem here is the migration. It migrates all items without knowing the correct values.
Yes, that's my understanding as well.
> The only way i see to fix this, is migrate per credential upon usage, and extract the correct values from the provided V5 layout.
Since the authenticator tells us what it expects to be the correct values, they could be set before authenticating to the correct ones, which would make this work correctly.
Normally this would be done automatically [here](https://github.com/dani-garcia/vaultwarden/blob/main/src/api/core/two_factor/webauthn.rs#L441), but because of the the "invalid state", it does not.
Unfortunately, manually setting the `backup_eligible` to the one provided by the authenticator upon first authentication would be quite hard since there would be some parsing to do without the help of the webauthn-rs crate. So this should ideally be possible to do in webauthn-rs itself for the purpose of migrating to the more extensive CredentialV5.
Could I use your image @zUnixorn then migrate the soft passkeys and then update to the latest vaultwarden/server image? That should work right?
@Blacks-Army commented on GitHub (Aug 11, 2025):
Could I use your image @zUnixorn then migrate the soft passkeys and then update to the latest vaultwarden/server image? That should work right?
Could I use your image @zUnixorn then migrate the soft passkeys and then update to the latest vaultwarden/server image? That should work right?
You could also manually modify the json. But keep in mind, hardware tokens will break with that image
@BlackDex commented on GitHub (Aug 11, 2025):
> Could I use your image [@zUnixorn](https://github.com/zUnixorn) then migrate the soft passkeys and then update to the latest vaultwarden/server image? That should work right?
You could also manually modify the json. But keep in mind, hardware tokens will break with that image
No I mean I would migrate the soft passkeys with the image where it works (and don't touch the security keys) and then change it to the latest of this repo, that should work or not?
@Blacks-Army commented on GitHub (Aug 11, 2025):
No I mean I would migrate the soft passkeys with the image where it works (and don't touch the security keys) and then change it to the latest of this repo, that should work or not?
No I mean I would migrate the soft passkeys with the image where it works (and don't touch the security keys) and then change it to the latest of this repo, that should work or not?
And how do you prevent the hardware keys from being touched then? All are migrated in one go.
@BlackDex commented on GitHub (Aug 11, 2025):
> No I mean I would migrate the soft passkeys with the image where it works (and don't touch the security keys) and then change it to the latest of this repo, that should work or not?
And how do you prevent the hardware keys from being touched then? All are migrated in one go.
No I mean I would migrate the soft passkeys with the image where it works (and don't touch the security keys) and then change it to the latest of this repo, that should work or not?
And how do you prevent the hardware keys from being touched then? All are migrated in one go.
Ahh okay, didn't know that.
@Blacks-Army commented on GitHub (Aug 11, 2025):
> > No I mean I would migrate the soft passkeys with the image where it works (and don't touch the security keys) and then change it to the latest of this repo, that should work or not?
>
> And how do you prevent the hardware keys from being touched then? All are migrated in one go.
Ahh okay, didn't know that.
I'm not sure what v0.3.2 was using as it's base verification policy, which we should try to not change i think.
But Bitwarden did renamed the MFA option from WebauthN/FIDO to Passkeys. While I'm of course for more security, it might make it more difficult for users to use the same WebauthN on both there iOS device and macOS. Or maybe an Android device which was transferred, of that modifies something maybe, or possibly at all.
I might check what the different outcome will be between the two options.
I opened kanidm/webauthn-rs#510, maybe I missed something but I am not sure how CredentialV3 is supposed to be migrated to CredentialV5 when the Passkey was backed up previously
Both of these are kind of symptomatic of the issue, which was the webauthn wg and fido completely rewriting the rules underneath everyone. We went from a standard that was "hardware bound, touch only" to now having software authenticators that lie about their capabilities, authenticators that move between places, pretty much forced PIV/biometrics on many authenticators and more. There is so much confusion about this topic, and even just to highlight how confusing it is, there are 5 different definitions of what a passkey is!!!
And that's a big problem, because we wrote webauthn-rs at a time when we started with the first (hardware only single factors, maybe you had a PIN if you were hardcore). And then we've had to adapt and change as we go, trying to understand what the threats/risks were and wrap up them nicely so that people can have nice safe interfaces that "just work". We've always tried to model the library around "user experiences and workflows" and we've been forced to change those flows as webauthn has become messier and more confusing, and even undermined their own original workflows.
So at the time when we wrote the v3 -> v5 credential layout migration, there were no roaming authenticators, so we made an assumption of "well, nothing was backup eligible, default to false". But it's now what 3 or so years since then, and now when you do that migration that assumption is no longer true. There are so many roaming authenticators out there (even vaultwarden!) that we can't make that assumption.
Anyway, I made a PR to hopefully resolve this for you which assumes every authenticator was backup eligible by default. I do not believe this undermines anything for you. But I will caution and warn you that the issue now with how passkeys work is that they require userVerification, they are no longer just a touch and move on. You must enter a PIN or some other form of UV. This means older u2f style keys will fail to work in future where you expect just to touch and proceed.
@Firstyear commented on GitHub (Aug 13, 2025):
> I'm not sure what v0.3.2 was using as it's base verification policy, which we should try to not change i think.
>
> But Bitwarden did renamed the MFA option from WebauthN/FIDO to Passkeys. While I'm of course for more security, it might make it more difficult for users to use the same WebauthN on both there iOS device and macOS. Or maybe an Android device which was transferred, of that modifies something maybe, or possibly at all.
>
> I might check what the different outcome will be between the two options.
> I opened [kanidm/webauthn-rs#510](https://github.com/kanidm/webauthn-rs/issues/510), maybe I missed something but I am not sure how CredentialV3 is supposed to be migrated to CredentialV5 when the Passkey was backed up previously
Both of these are kind of symptomatic of the issue, which was the webauthn wg and fido completely rewriting the rules underneath everyone. We went from a standard that was "hardware bound, touch only" to now having software authenticators that lie about their capabilities, authenticators that move between places, pretty much forced PIV/biometrics on many authenticators and more. There is so much confusion about this topic, and even just to highlight how confusing it is, there are 5 different definitions of what a passkey is!!!
And that's a big problem, because we wrote webauthn-rs at a time when we started with the first (hardware only single factors, maybe you had a PIN if you were hardcore). And then we've had to adapt and change as we go, trying to understand what the threats/risks were and wrap up them nicely so that people can have nice safe interfaces that "just work". We've always tried to model the library around "user experiences and workflows" and we've been forced to change those flows as webauthn has become messier and more confusing, and even undermined their own original workflows.
So at the time when we wrote the v3 -> v5 credential layout migration, there were no roaming authenticators, so we made an assumption of "well, nothing was backup eligible, default to false". But it's now what 3 or so years since then, and now when you do that migration that assumption is no longer true. There are so many roaming authenticators out there (even vaultwarden!) that we can't make that assumption.
Anyway, I made a PR to hopefully resolve this for you which assumes every authenticator was backup eligible by default. I do not believe this undermines anything for you. But I will caution and warn you that the issue now with how passkeys work is that they require userVerification, they are no longer just a touch and move on. You must enter a PIN or some other form of UV. This means older u2f style keys will fail to work in future where you expect just to touch and proceed.
I think i have tackled this issue now.
With some voodoo done during the response and verification Software/Passkeys should now work without issues after a migration. If you already migrated before, this should still fix those issues, as nothing is changed on how the migration converted the v3 to the v5 credentials.
@BlackDex commented on GitHub (Aug 16, 2025):
I think i have tackled this issue now.
With some voodoo done during the response and verification Software/Passkeys should now work without issues after a migration. If you already migrated before, this should still fix those issues, as nothing is changed on how the migration converted the v3 to the v5 credentials.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Originally created by @IamTaoChen on GitHub (Aug 9, 2025).
Prerequisites
Vaultwarden Support String
Your environment (Generated via diagnostics page)
Config & Details (Generated via diagnostics page)
Show Config & Details
Config:
Vaultwarden Build Version
testing
Deployment method
Official Container Image
Custom deployment method
No response
Reverse Proxy
nginx
Host/Server Operating System
Linux
Operating System Version
Mac
Clients
Browser Extension
Client Version
No response
Steps To Reproduce
SSO_ENABLED = falseand use master password to loginExpected Result
should login
Actual Result
no action after webauth
Logs
Screenshots or Videos
No response
Additional Context
No response
@IamTaoChen commented on GitHub (Aug 9, 2025):
It cannot downgrade to
lastest@BlackDex commented on GitHub (Aug 9, 2025):
You need to either remove 2fa, use a different 2fa type, or restore the record from a backup.
I'm not seeing a failed attempt using webauthn in de first log. I only see duo there. So, not sure what you tried there.
@Blacks-Army commented on GitHub (Aug 9, 2025):
I have the same Error, but only if I use the WebAuthn Passkey from my Bitwarden Account in Bitwarden. If I use my YubiKey it works.
Edit: It also happens with the yubikey here the log:
@Blacks-Army commented on GitHub (Aug 9, 2025):
Maybe my issue is related to PR #5934?
@BlackDex commented on GitHub (Aug 9, 2025):
I see strange stuff regarding non-utf-8 characters in your logs. Those shouldn't be happening at all. That might also break the WebauthN data sent to the server.
Not sure what is in between the browser and the Vaultwarden instance, but it looks like it is adjusting your WebAuthN response to the server.
@Blacks-Army commented on GitHub (Aug 9, 2025):
I am using Traefik, but this also did not happened before. Traefik only sets Headers and Rate Limits.
Edit: And I am not setting a Header named cookie
@BlackDex commented on GitHub (Aug 9, 2025):
I'm unable to reproduce this on my side.
I used the current stable version to create the 2FA tokens, and i used both SoftU2F and my YubiKey5c. Then compiled the main branch, and logged in just fine.
Be sure you have HTTPS working with a valid certificate. Check
/admin/diagnosticsto see if there are issues.@IamTaoChen i would suggest to make the admin interface available so that you can also check the diagnostics page. It provides us useful information which otherwise makes it almost impossible for us to help in a useful way.
@Blacks-Army, In your case it looks like it has something to do with a backup key. Not sure which key you are using right now, is that the key used during the creation, or are you now using the backup key to verify.
I noticed something similar in Proxmox regarding this message. But, i do not have the means to test this my self, as i do not have two YubiKey's.
@BlackDex commented on GitHub (Aug 9, 2025):
As a reference to where i found it at Proxmox: https://git.proxmox.com/?p=proxmox.git;a=commit;h=e4d5cce8ec7b97353b4c922424bf1bc4dcede420
@zUnixorn, maybe you have an idea regarding this?
@BlackDex commented on GitHub (Aug 9, 2025):
I't might be as easy as allowing
allow_backup_eligible_upgradebut I'm not sure.@BlackDex commented on GitHub (Aug 9, 2025):
Also, looking at the code and description from YubiKey it self. It might be you registered the spare key (which you might be using right now) after you verified the main key in Vaultwarden. If that is the case, then i think this error is legit, and increases security of Vaultwarden. I think the previous implementation just didn't bothered looking at it, and that is why you were able to use it and login.
@Blacks-Army commented on GitHub (Aug 9, 2025):
In the Admin Diagnostics I only get this
API calls:
Header: 'x-xss-protection' does not contain '0'
2FA Connector calls:
Header: 'x-xss-protection' does not contain '0'
Header: 'x-frame-options' is present while it should not
@Blacks-Army commented on GitHub (Aug 9, 2025):
Well in Vaultwarden I have multiple Keys. Why does it matter which I use I do not understand tbh.
PassKey - IOS Keychain
YubiKey 5C NFC (Main)
Bitwarden - in the vault itself
@IamTaoChen commented on GitHub (Aug 9, 2025):
Sorry, it’s evening here in China and I’ve had a few beers. I’ll run the tests and add more logs tomorrow.
@IamTaoChen commented on GitHub (Aug 9, 2025):
I have some update about Client Actions (Mac Chrome Extension & Desktop App)
I am not certain whether these actions are directly related to the this issue being discussed.
Sorry, this comment is using the latest image not testing(I forgot to mention it before)
@BlackDex commented on GitHub (Aug 9, 2025):
@Blacks-Army, i would suggest to fix those items, as they might cause some weird issues with some clients.
I tried on a macOS device, and I'm able to add a key, but afterwards I'm unable to login with it.
Using my YubiKey5c on the mac works fine though.
@BlackDex commented on GitHub (Aug 9, 2025):
Maybe @yaleman or @Firstyear have some clue regarding this? (Sorry for tagging)
@BlackDex commented on GitHub (Aug 9, 2025):
@IamTaoChen, if you are not using
:latest, then it can't have anything to do with the latest webauthn-rs version, as that currently is only in the:testingtagged images.I wouldn't be surprised if Apple doesn't fully adhere to the standards which might cause issues.
@Blacks-Army commented on GitHub (Aug 9, 2025):
Removing the Headers did not fix anything.
However I noticed that this only happens on Software saved Passkeys like a Passkey saved in the Apple Keychain or in Bitwarden itself.
It works with YubiKey as a Passkey and Yubico OTP.
@IamTaoChen commented on GitHub (Aug 9, 2025):
I update the diagnostics page
@IamTaoChen commented on GitHub (Aug 9, 2025):
Testing Image
Login with master password
2FA by PassKey
2FA by Authebticater APP
Wroks.
@IamTaoChen commented on GitHub (Aug 9, 2025):
@BlackDex Hi, I have noticed that the diagnostics page(Support String) displays certain OIDC information that is not concealed.(not about this issue, but maybe important)
@IamTaoChen commented on GitHub (Aug 9, 2025):
I'll revcover my DB. if any other information need, please let me know.
@BlackDex commented on GitHub (Aug 9, 2025):
@Timshel
@stefan0xC commented on GitHub (Aug 9, 2025):
I think we should be masking (at least)
sso_client_idandsso_authorityhere https://github.com/dani-garcia/vaultwarden/blob/a133d4e90c6f864c87ad54a877ea501f4d4f92ec/src/config.rs#L268-L272@IamTaoChen commented on GitHub (Aug 9, 2025):
and
sso_callback_pathwhich contains the domain of vaultwarden@Blacks-Army commented on GitHub (Aug 9, 2025):
You mean the secret right?
@stefan0xC commented on GitHub (Aug 9, 2025):
Nope. That already is of type
Passso it's hidden already. I've updated my PR #6153 to also mask those three options.@Timshel commented on GitHub (Aug 9, 2025):
When debugging an issue the
sso_authoritycan be useful, but it can be sensitive too.The
sso_client_idwill almost certainly never be useful.@zUnixorn commented on GitHub (Aug 9, 2025):
Looking at the code (here), this seems to be the problem, I am not sure what this means though.
The value of
allow_backup_eligible_upgradeseems to be a difference between start_securitykey_authentication() and start_passkey_authentication().start_passkey_authentication()allowsallow_backup_eligible_upgradewhilestart_securitykey_authenticationdisallowsallow_backup_eligible_upgrade.Unfortunately I don't know what security implications this has. Originally
start_passkey_authentication()was used but it required modifying the JSON, nowstart_securitykey_authentication()is used since it produces the same JSON webauthn request as before the crate update, but as it seems, it doesn't allowallow_backup_eligible_upgrade.In the CredentialV3 this wasn't present, in the migration, a value of
falseis assumed forbackup_eligible.I think allowing
allow_backup_eligible_upgradeusing the underlying webauthn_core_rs crate or usingstart_passkey_authentication()as before, could fix this.Since you already pinged two Maintainers of webauthn-rs, maybe they know what security implication
allow_backup_eligible_upgradehas and why this is allowed forstart_passkey_authentication()but notstart_securitykey_authentication()The docs mention the following regarding the *_security_key() methods:
Maybe this is the reason that security keys work but passkeys don't always?
Contrary to the initial suggestion, using
start_passkey_authentication()may be more compatible with the original implementation with webauthn-rs 0.3@zUnixorn commented on GitHub (Aug 9, 2025):
I reverted to using the original start_passkey_authentication() approach in this branch: https://github.com/zUnixorn/vaultwarden/tree/use_passkey_methods_webauthn_rs
I couldn't reproduce this issue on my end and cannot test this, but you could try if the version in my branch exhibits the same behavior or if it actually fixes the problem.
@Firstyear commented on GitHub (Aug 10, 2025):
The difference is that it allows a key that was previously hardware bound to a single device, to now be transient to many devices. As an RP, this changes the security assumptions of the device, since what you thought was single device, now has moved.
In general for the vaultwarden use case, you probably want "securitykey" options here, because that treats the device as a single factor, where as passkeys treat the device as self-contained multifactor and will force user verification.
@Blacks-Army commented on GitHub (Aug 10, 2025):
I don't know if I quite understand that, but does that mean we need to use
start_passkey_authentication()for soft tokens (passkeys in password managers) to work?@BlackDex commented on GitHub (Aug 10, 2025):
I'm not sure what v0.3.2 was using as it's base verification policy, which we should try to not change i think.
But Bitwarden did renamed the MFA option from WebauthN/FIDO to Passkeys. While I'm of course for more security, it might make it more difficult for users to use the same WebauthN on both there iOS device and macOS. Or maybe an Android device which was transferred, of that modifies something maybe, or possibly at all.
I might check what the different outcome will be between the two options.
@Blacks-Army commented on GitHub (Aug 10, 2025):
Is there an image available? Or do I need to build it myself? If so I don't know how...
@zUnixorn commented on GitHub (Aug 10, 2025):
Currently there is no image available, but I can build one later so it's easier to test. I will comment it here, when i've come around to make one available.
@BlackDex commented on GitHub (Aug 10, 2025):
I tried with the patch form @zUnixorn, but that doesn't solve the Apple macOS/iOS issue for me.
Either it's not fully supported by webauthn-rs, or it's not using standards.
Unfortunately the test/demo application isn't working of webauthn-rs, which prevents me, and others from seeing which data is transfered/used to try and authenticate. This might help the developers of webauthn-rs to fix issues or maybe supply some details regarding issues with some of these devices.
@Blacks-Army commented on GitHub (Aug 10, 2025):
Kanidm uses also webauthn-rs 0.5.2 and there it does work
https://github.com/kanidm/kanidm/blob/38b54969d1e3e5ca9aeb6189fec49e43577d5652/Cargo.toml#L305C1-L305C69
@Blacks-Army commented on GitHub (Aug 10, 2025):
I am just seeing this part maybe we need this dependency?
https://github.com/kanidm/kanidm/blob/38b54969d1e3e5ca9aeb6189fec49e43577d5652/Cargo.toml#L302
@BlackDex commented on GitHub (Aug 10, 2025):
@Blacks-Army, that seems to be the client-side of the code, not server-side, and thus not something useful.
I do think i see some strange things though. Currently debugging to see what the difference are.
@BlackDex commented on GitHub (Aug 10, 2025):
Yes, I have found a bug which prevents almost every device except for some hardware keys to continue.
Here:
https://github.com/dani-garcia/vaultwarden/blob/e35c6f87054563af6ecfec9f100779523b62c473/src/api/core/two_factor/webauthn.rs#L437
There is a check if the credential id's match, but, it also checks if there is an updated needed, but if that part fails, the whole authentication fails. And, if i read the documentation correctly, almost no device uses this feature, and it's also not really needed.
Ill check what happens if i update this. But, that still leaves me a bit undecided if using
securityorpasskeywould be the right way to go. But, since we probably want to have Soft tokens like Apple work cross device, and that needs to allow thebackup_stateto be able to be set totrue, I thinkpasskeysis probably the way to go.@BlackDex commented on GitHub (Aug 10, 2025):
I Found the issue and created a PR to fix this.
@BlackDex commented on GitHub (Aug 10, 2025):
Once https://github.com/dani-garcia/vaultwarden/actions/runs/16864227130 is finished you should be able to test if this fully resolves the issues reported here by using the
:testingtagged images.@IamTaoChen commented on GitHub (Aug 10, 2025):
No, It still doesn't work
@Blacks-Army commented on GitHub (Aug 10, 2025):
For me it is not working... Same Error
@BlackDex commented on GitHub (Aug 10, 2025):
And you both are sure you did a
docker pullordocker compose pullfirst?What is the version running? Does it has the last commit tag currently in our main branch?
Also @Blacks-Army, do you still have those strange header errors? And did you fixed those warnings the diagnostics page reported?
@Blacks-Army commented on GitHub (Aug 10, 2025):
Yes I did and I even checked the Digset
@BlackDex commented on GitHub (Aug 10, 2025):
Also, i think that the issue regarding @IamTaoChen still is because of the backup key is being used in some way. And not the original key used, but I'm not 100% sure regarding that.
Then maybe @zUnixorn could rebase on main, and provide a working image maybe of his security > passkey changes?
That is then the only item i think could be the issue then.
But, at least Bitwarden Passkeys and macOS/iOS Passkeys work again without issues.
@Blacks-Army commented on GitHub (Aug 10, 2025):
For me iOS and Bitwarden Passkeys didn't work... did you maybe add them on a freshly installed instance? I am doing a Upgrade and want to use my already saved passkeys.
@BlackDex commented on GitHub (Aug 10, 2025):
Well they were migrated. But i can of course try that again.
But that will probably be tomorrow.
@zUnixorn commented on GitHub (Aug 11, 2025):
Ok, I rebased and generated the docker images with the
*_passkey_*methods based on alpine or debian:docker pull ghcr.io/zunixorn/vaultwarden:testing-alpinedocker pull ghcr.io/zunixorn/vaultwarden:testingDoes the problem persist with these versions?
@Blacks-Army commented on GitHub (Aug 11, 2025):
Not for me...
@zUnixorn commented on GitHub (Aug 11, 2025):
Now it does not fail with
CredentialBackupElligibilityInconsistentbut instead withCredentialMayNotBeHardwareBoundso switching to the passkey methods did indeed fix that previous error. Since your passkey seems to have thebackup_stateset totrue, but the migration defaults tofalseforbackup_eligible, the webauthn crate doesn't allow you to verify with that passkey.I updated the docker image to migrate the passkeys with
backup_eligibleexplicitly set totrue, I don't know if that's something that should be done, but if this fixes the problem, we at least know where the problem lies.So if you update the image, the
CredentialMayNotBeHardwareBounderror hopefully goes away too.@Blacks-Army commented on GitHub (Aug 11, 2025):
It works now! Thanks!
@zUnixorn commented on GitHub (Aug 11, 2025):
Thank you for testing this! Maybe this fixed @IamTaoChen's problem as well.
Now the question is, if it is desired to always migrate the old passkeys withbackup_eligibleset totrueand using the*_passkey_*methods instead of*_securitykey_*. Since the webauthn 0.3 version didn't even check nor track this, I would think that allowing this wouldn't make this less secure then the v0.3 implementation, but I am not sure if this assumption is actually correct.@BlackDex commented on GitHub (Aug 11, 2025):
I would say, use passkeys, as that is also what Bitwarden calls it these days in the Security Settings of the user.
It would also help implementing the actual passkey login feature i guess, to not have multiple types of webauthn formats.
And, indeed, to stay compatible with the previous versions, i would say switch to
*_passkey_*. If you can create a PR for that, then Ill do some tests my self too, and if ok, ill merge :).@zUnixorn commented on GitHub (Aug 11, 2025):
I will create a PR for that later today.
Should I also change thebackup_eligiblevalue (this field) like I did in my current branch or leave it and only change to the passkey methods?@BlackDex commented on GitHub (Aug 11, 2025):
If I'm correct leaving that off and switch to passkeys should solve it right? Since passkeys has that enabled by default?
@Blacks-Army commented on GitHub (Aug 11, 2025):
No, by default it is
false@zUnixorn commented on GitHub (Aug 11, 2025):
Ok after thinking about it, switching to passkeys should be enough. When I only applied that change, there was still
CredentialMayNotBeHardwareBoundthrown as an error. But after looking through the webauthn-rs code, this shouldn't happen ifupdate_credential()is called, since it should updatebackup_eligiblewhich would preventCredentialMayNotBeHardwareBoundfrom being thrown.@Blacks-Army when you tested the first change that didn't work, did you try to authenticate multiple times or just once? I can't test it myself but I would think that it should only fail the first time but update
backup_eligible. The second time it should work, because the value is set correctly then.@Blacks-Army commented on GitHub (Aug 11, 2025):
I don't know anymore. I would need to test it again
@zUnixorn commented on GitHub (Aug 11, 2025):
Ok, could you try this image again:
docker pull ghcr.io/zunixorn/vaultwarden@sha256:c6a9c574220ad650e7ee321366d3216b47d848651d2d5cb5ebf5ba674f5f4535And then try to authenticate 2 times, the first time should fail but update the stored backup eligibility and the second time should hopefully work then.
@zUnixorn commented on GitHub (Aug 11, 2025):
If that's not the case the Credential doesn't get updated properly and then that should be fixed instead.
@Blacks-Army commented on GitHub (Aug 11, 2025):
Nope it does not work I am getting the same Error Message
@BlackDex commented on GitHub (Aug 11, 2025):
Hmm.. I'm not sure we should set it for all items as you currently do @zUnixorn.
My YubiKey for example has that set to false, which i would like to keep it that way. As that is a physical token and not software.
The
*_passkey_*functions allow that boolean to be true. But, it doesn't force it to be true.Setting everything to true, could cause a security issue i think.
@zUnixorn commented on GitHub (Aug 11, 2025):
Yeah, that's not the correct way. But I think I know what the problem is now.
When
allow_backup_eligible_upgradethebackup_eligibleshould be updated whenupdate_credential()is called. This works when thebackup_stateis stillfalsee.g. the Passkey wasn't backed up. Because in that case the Authentication doesn't throw an error which allowsupdate_credential()to be called and sincebackup_eligibleis now set to the correct value, future authentications even after the key was backed up e.g.backup_stateistrue, will work fine.But if the key was already backed up before the migration,
backup_stateis already true, which fails the Authentication and therefore prevents updating the Credential, which would actually solve the problem.I am not sure if this is intended or a bug, but I am thinking about opening an issue in webauthn-rs since migrating already backed up passkeys seems to be impossible with my current understanding of its implementation.
For Reference:
backup_stateis true whenbackup_eligiblewasn't updated, regardless ofallow_backup_eligible_upgrade's value.backup_eligible, finish_passkey_authentication() must succeed to get the updated AuthenticationResult and use it to update the credential'sbackup_eligiblewith update_credential()@BlackDex commented on GitHub (Aug 11, 2025):
The problem here is, that my Hardware token, a YubiKey5c, has it set to false. You arn't allowed to set
truetofalsefor this flag. Fromfalsetotrueis allowed.Using your latest patched version breaks my YubiKey login, but all the others are working now.
The main problem here is the migration. It migrates all items without knowing the correct values.
The only way i see to fix this, is migrate per credential upon usage, and extract the correct values from the provided V5 layout.
@zUnixorn commented on GitHub (Aug 11, 2025):
I opened https://github.com/kanidm/webauthn-rs/issues/510, maybe I missed something but I am not sure how CredentialV3 is supposed to be migrated to CredentialV5 when the Passkey was backed up previously
@zUnixorn commented on GitHub (Aug 11, 2025):
Yes, that's my understanding as well.
Since the authenticator tells us what it expects to be the correct values, they could be set before authenticating to the correct ones, which would make this work correctly.
Normally this would be done automatically here, but because of the the "invalid state", it does not.
Unfortunately, manually setting the
backup_eligibleto the one provided by the authenticator upon first authentication would be quite hard since there would be some parsing to do without the help of the webauthn-rs crate. So this should ideally be possible to do in webauthn-rs itself for the purpose of migrating to the more extensive CredentialV5.@Blacks-Army commented on GitHub (Aug 11, 2025):
Could I use your image @zUnixorn then migrate the soft passkeys and then update to the latest vaultwarden/server image? That should work right?
@BlackDex commented on GitHub (Aug 11, 2025):
You could also manually modify the json. But keep in mind, hardware tokens will break with that image
@Blacks-Army commented on GitHub (Aug 11, 2025):
No I mean I would migrate the soft passkeys with the image where it works (and don't touch the security keys) and then change it to the latest of this repo, that should work or not?
@BlackDex commented on GitHub (Aug 11, 2025):
And how do you prevent the hardware keys from being touched then? All are migrated in one go.
@Blacks-Army commented on GitHub (Aug 11, 2025):
Ahh okay, didn't know that.
@Firstyear commented on GitHub (Aug 13, 2025):
Both of these are kind of symptomatic of the issue, which was the webauthn wg and fido completely rewriting the rules underneath everyone. We went from a standard that was "hardware bound, touch only" to now having software authenticators that lie about their capabilities, authenticators that move between places, pretty much forced PIV/biometrics on many authenticators and more. There is so much confusion about this topic, and even just to highlight how confusing it is, there are 5 different definitions of what a passkey is!!!
And that's a big problem, because we wrote webauthn-rs at a time when we started with the first (hardware only single factors, maybe you had a PIN if you were hardcore). And then we've had to adapt and change as we go, trying to understand what the threats/risks were and wrap up them nicely so that people can have nice safe interfaces that "just work". We've always tried to model the library around "user experiences and workflows" and we've been forced to change those flows as webauthn has become messier and more confusing, and even undermined their own original workflows.
So at the time when we wrote the v3 -> v5 credential layout migration, there were no roaming authenticators, so we made an assumption of "well, nothing was backup eligible, default to false". But it's now what 3 or so years since then, and now when you do that migration that assumption is no longer true. There are so many roaming authenticators out there (even vaultwarden!) that we can't make that assumption.
Anyway, I made a PR to hopefully resolve this for you which assumes every authenticator was backup eligible by default. I do not believe this undermines anything for you. But I will caution and warn you that the issue now with how passkeys work is that they require userVerification, they are no longer just a touch and move on. You must enter a PIN or some other form of UV. This means older u2f style keys will fail to work in future where you expect just to touch and proceed.
@BlackDex commented on GitHub (Aug 16, 2025):
I think i have tackled this issue now.
With some voodoo done during the response and verification Software/Passkeys should now work without issues after a migration. If you already migrated before, this should still fix those issues, as nothing is changed on how the migration converted the v3 to the v5 credentials.
@Blacks-Army commented on GitHub (Aug 18, 2025):
I have just reregistered all PassKeys it works now