Skip to content

largeBlob can never be written: createCredential does not request the extension at registration #48

Description

@NiKrause

writeLargeBlobMetadata() always reports written: false for credentials created by this package, because WebAuthnDIDProvider.createCredential() never asks for the largeBlob extension at registration.

Version 0.5.0.

Measured

Wrapping navigator.credentials in a consumer app (simple-todo) and creating a passkey through the documented create-or-recover flow:

1. create  requested=[prf]        largeBlob=null
2. get     requested=[largeBlob]  largeBlob={"written":false}
3. get     requested=[prf]        (keystore)
4. get     requested=[]           (signIdentity)

Call 2 is the write. It costs the user a biometric prompt and stores nothing.

Why

WebAuthn only permits writing a blob to a credential that was registered with largeBlob: { support: "preferred" | "required" }. In createCredential, the extension is added for the other two methods and not for this one:

if (keystoreEncryptionMethod === KEYSTORE_ENCRYPTION_METHODS.PRF) {
  // adds prf
} else if (keystoreEncryptionMethod === KEYSTORE_ENCRYPTION_METHODS.HMAC_SECRET) {
  // adds hmac-secret
}
// Note: largeBlob write happens after credential creation

The comment is accurate about when the write happens, but the registration half never arrives — so keystoreEncryptionMethod: 'largeBlob' cannot work either.

Why it went unnoticed

writeLargeBlobMetadata returns { assertion, extensionResults } and the outcome lives in extensionResults.largeBlob.written. The demo flow in examples/ — which consumers copy, as the TODO(upstream) in simple-todo's passkey-identity.js records — discards the return value and only catches exceptions. Nothing throws on a failed write, so it reads as success and silently falls through to the localStorage fallback.

The user-visible consequence is a passkey that does not survive a cleared browser profile, while the code and the copy both suggest it does.

Suggested

  1. Request largeBlob: { support: 'preferred' } in createCredentialpreferred rather than required so authenticators without it still register.
  2. Surface the registration result (getClientExtensionResults().largeBlob.supported) on the returned credential info, so an app can offer the backup step only where it can succeed.
  3. Have writeLargeBlobMetadata reject, or return an explicit boolean, when written is false — a silent no-op is the part that cost the diagnosis.
  4. Export the create-or-recover flow as an official helper. It exists only in examples/, so every consumer copies these three calls along with this bug.

Consumer side, for reference

simple-todo removed the write rather than moving it behind an explicit button: a button offering to back the passkey up would fail in exactly the same way, and an action that asks for a fingerprint and achieves nothing silently is worse than no action. Creating a passkey there is now three prompts instead of four. The explicit backup step is worth adding once registration requests the extension.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions