1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-10 05:30:01 +00:00

Experiment: UX Poc to use passkeys for unlocking vault

This commit is contained in:
Anders Åberg
2025-03-19 19:29:19 +01:00
parent 7c0af6c8fb
commit 7bb2a25991
2 changed files with 58 additions and 26 deletions

View File

@@ -125,36 +125,26 @@
"
>
<form [bitSubmit]="submit" [formGroup]="formGroup">
<bit-form-field>
<bit-label>{{ "masterPass" | i18n }}</bit-label>
<input
type="password"
formControlName="masterPassword"
bitInput
appAutofocus
name="masterPassword"
class="tw-font-mono"
required
appInputVerbatim
/>
<button
type="button"
bitIconButton
bitSuffix
bitPasswordInputToggle
[(toggled)]="showPassword"
></button>
<!-- [attr.aria-pressed]="showPassword" -->
</bit-form-field>
<div class="tw-flex tw-flex-col tw-space-y-3">
<button type="submit" bitButton bitFormButton buttonType="primary" block>
{{ "unlock" | i18n }}
</button>
<ng-container>
<button
type="button"
bitButton
bitFormButton
buttonType="primary"
block
(click)="unlockViaPasskey()"
>
<span>Unlock with passkey</span>
</button>
</ng-container>
<p class="tw-text-center">{{ "or" | i18n }}</p>
<button type="submit" bitButton bitFormButton buttonType="secondary" block>
Unlock with master password
</button>
<ng-container *ngIf="showBiometrics">
<button
type="button"

View File

@@ -351,6 +351,48 @@ export class LockComponent implements OnInit, OnDestroy {
}
}
async unlockViaPasskey(): Promise<void> {
// get a 32 byte unlock salt from the vault
// note: Depending on how we want to derive keys etc, this could be the same or a different salt than the one used for the sign in feature.
const unlockKeySalt = new Uint8Array(32).fill(0); // Mocked
// get a list of credential ids that can be used to unlock the vault. These could be synced from the server and stored on device.
// Knowing these will improve the UX by only showing the relevant credentials to the user.
const credentialIdsUsedforUnlock = [
"7ocVvTwCOoKfKaAp-FRMTPaFqB7CNOF1Z5SkAWRhIaSAko-6yyYvSnJoSzAqc6dD", // Anders yubikey
//"g773cRzuHU2qAClY7P4hVzMhw-I", // Anders apple keychain
];
const allowedCredentials = credentialIdsUsedforUnlock.map((passkey) => {
const x = passkey.replace(/-/g, "+").replace(/_/g, "/");
return {
id: Uint8Array.from(atob(x), (c) => c.charCodeAt(0)),
type: "public-key",
};
});
// Note: No input here can really be trusted to offer much security since it's generated by the client.
const publicKey = {
challenge: new Uint8Array(32).fill(0),
rpId: "vault.bitwarden.com",
allowCredentials: allowedCredentials,
timeout: 60000,
userVerification: "required",
extensions: {
prf: { eval: { first: unlockKeySalt } },
},
} as any;
const response = (await navigator.credentials.get({ publicKey })) as PublicKeyCredential;
// dervied key
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const prfResult = (response.getClientExtensionResults() as any).prf?.results?.first;
// use the prfresult in a similar way to the sign in flow.
}
async unlockViaBiometrics(): Promise<void> {
this.unlockingViaBiometrics = true;