@le-space/orbitdb-identity-provider-webauthn-did
v0.2.13
Published
WebAuthn-based DID identity provider for OrbitDB for hardware-secured wallets and biometric Passkey authentication
Maintainers
Readme
OrbitDB WebAuthn Identity Providers
⚠️ Security: Experimental release. No formal audit. Use only after your own review.
This package provides:
- Two WebAuthn-based OrbitDB identity providers.
- A standalone WebAuthn toolkit export (
@le-space/orbitdb-identity-provider-webauthn-did/standalone) for reuse outside OrbitDB identity wiring. - WebAuthn-Varsig: No insecure OrbitDB keystore at all. Each entry is signed by WebAuthn (varsig envelope), so keys never leave the authenticator, one Passkey (WebAuthn) prompt per write.
- Keystore-based DID: Generates an Ed25519/secp256k1 keystore keypair for OrbitDB signing in browser memory. When
encryptKeystoreis enabled, the private key is encrypted with AES-GCM and only rehydrated in memory after a WebAuthn unlock (PRF, largeBlob, or hmac-secret).
Current WebAuthn Model
- Discoverable credentials are enabled by default across the shared WebAuthn config.
- Authentication/assertion requests omit
allowCredentialsby default, so the browser/authenticator can resolve the credential discoverably. - You can switch this centrally with
configureWebAuthn({ discoverableCredentials: true|false }). - Registration is still the point where this package extracts the credential public key from attestation.
- Later
navigator.credentials.get()assertions do not reliably return the public key again, so identity reconstruction still needs metadata from somewhere else.
In this repo today, metadata recovery works in two layers:
- Preferred recovery path: store identity metadata in WebAuthn
largeBloband recover it later through discoverable authentication. - Fallback recovery path: store the same metadata in browser
localStorage.
This means discoverable credentials remove the need to pre-select the credential for authentication, but they do not by themselves eliminate the need for identity metadata persistence.
Recommendation (security-first):
- Best security: Varsig provider (hardware-backed key for every write).
- Best balance: Keystore provider with WebAuthn-encrypted keystore (fewer prompts, faster writes, key material in memory during session).
Note: WebAuthn varsig support in this repo relies on our forked @le-space/iso-* packages of Hugo Dias iso-repo (notably @le-space/iso-did and @le-space/iso-webauthn-varsig) to align with the updated varsig flow.
Install
npm install @le-space/orbitdb-identity-provider-webauthn-didNote: @orbitdb/core is patched (via patch-package) to support Ed25519 keystore keys.
WebAuthn Configuration
The package now exposes a central WebAuthn policy API:
import {
configureWebAuthn,
getWebAuthnConfig,
resetWebAuthnConfig,
} from '@le-space/orbitdb-identity-provider-webauthn-did';
configureWebAuthn({ discoverableCredentials: true }); // default
console.log(getWebAuthnConfig());
resetWebAuthnConfig();Behavior:
discoverableCredentials: true- registration requests resident/discoverable credentials
- assertion requests omit
allowCredentials
discoverableCredentials: false- assertion requests target a specific credential ID via
allowCredentials
- assertion requests target a specific credential ID via
Memory Keystore Quick Start
import {
WebAuthnDIDProvider,
OrbitDBWebAuthnIdentityProviderFunction,
} from '@le-space/orbitdb-identity-provider-webauthn-did';
const credential = await WebAuthnDIDProvider.createCredential({
userId: '[email protected]',
displayName: 'Alice',
});
const identity = await identities.createIdentity({
provider: OrbitDBWebAuthnIdentityProviderFunction({
webauthnCredential: credential,
}),
});Discoverable Recovery Notes
For the DID-based flow:
createCredential()can extract the public key from attestation and derive a DID from it.- later discoverable
navigator.credentials.get()proves possession of the credential, but usually returns onlyrawId,authenticatorData,clientDataJSON,signature, and maybeuserHandle - that assertion is not enough on its own to reconstruct the DID
To address this, the demos now attempt to:
- write identity metadata to
largeBlobafter passkey creation - recover that metadata later through discoverable authentication
- fall back to local browser storage if
largeBlobis unavailable or empty
Hardware-Secured Varsig Quick Start
import {
WebAuthnVarsigProvider,
createWebAuthnVarsigIdentity,
} from '@le-space/orbitdb-identity-provider-webauthn-did';
const credential = await WebAuthnVarsigProvider.createCredential({
userId: '[email protected]',
displayName: 'Alice',
});
const identity = await createWebAuthnVarsigIdentity({ credential });For varsig, the same recovery limitation applies: a discoverable assertion identifies the credential but does not re-export the public key. The demos therefore use the same largeBlob-first, local fallback recovery approach for varsig credential metadata.
Standalone Toolkit (without OrbitDB identity provider wiring)
Use the standalone export when you want WebAuthn signer and worker-keystore features independently from OrbitDB identity provider setup.
import {
createWebAuthnSigner,
WebAuthnHardwareSignerService,
createWorkerKeystoreClient,
} from '@le-space/orbitdb-identity-provider-webauthn-did/standalone';
// Create a hardware-backed WebAuthn varsig signer
const signer = await createWebAuthnSigner({
userId: '[email protected]',
displayName: 'Alice',
});
// Optional: bridge to UCAN signer surface
const ucantoSigner = signer.toUcantoSigner();
// Optional: persisted hardware signer lifecycle
const hardwareService = new WebAuthnHardwareSignerService();
await hardwareService.initialize({
userId: '[email protected]',
displayName: 'Alice',
});
// Optional: worker-based Ed25519 keystore client
const workerClient = createWorkerKeystoreClient();Domain Label Guidance (OrbitDB vs UCAN)
toUcantoSigner() supports an optional domainLabel override:
- OrbitDB entry signing: use the default domain label (
orbitdb-entry:). - UCAN flows that require a protocol-specific challenge prefix: pass it explicitly (for example
ucan-webauthn-v1:).
// OrbitDB-style default (no override)
const orbitdbUcantoSigner = signer.toUcantoSigner();
// UCAN-specific override
const ucanUcantoSigner = signer.toUcantoSigner({
domainLabel: 'ucan-webauthn-v1:',
});The verifier side and app protocol should define which domain label is required. IPFS deployment does not change this requirement.
Keystore-based DID (WebAuthn + OrbitDB keystore)
sequenceDiagram
autonumber
participant User
participant App
participant WebAuthn
participant Auth as Authenticator
participant KS as OrbitDB Keystore
participant Enc as KeystoreEncryption
participant DB as OrbitDB
User->>App: Create credential
App->>WebAuthn: create()
WebAuthn->>Auth: Create passkey
Auth-->>WebAuthn: Attestation
WebAuthn-->>App: Credential
App->>KS: getKey()/createKey(Ed25519)
KS-->>App: Keystore keypair
opt encryptKeystore=true
App->>Enc: generateSecretKey()
Enc-->>App: sk
App->>Enc: encrypt keystore private key (AES-GCM)
alt prf
App->>WebAuthn: get() with PRF
WebAuthn->>Auth: User verification
Auth-->>WebAuthn: PRF output
WebAuthn-->>App: PRF bytes
App->>Enc: wrap sk with PRF
else largeBlob
App->>WebAuthn: get() with largeBlob write
WebAuthn->>Auth: User verification
Auth-->>WebAuthn: Store sk in largeBlob
WebAuthn-->>App: largeBlob stored
else hmac-secret
App->>WebAuthn: get() with hmac-secret
WebAuthn->>Auth: User verification
Auth-->>WebAuthn: HMAC output
WebAuthn-->>App: HMAC bytes
App->>Enc: wrap sk with HMAC
end
end
App->>DB: db.put()
DB->>KS: sign entry with keystore key
KS-->>DB: Entry signature
Note over App,KS: Keystore private key is encrypted at rest when `encryptKeystore=true`.Varsig (no keystore)
sequenceDiagram
autonumber
participant User
participant App
participant WebAuthn
participant Auth as Authenticator
participant Var as Varsig Provider
participant DB as OrbitDB
User->>App: Create credential
App->>WebAuthn: create()
WebAuthn->>Auth: Create passkey
Auth-->>WebAuthn: Attestation
WebAuthn-->>App: Credential
User->>App: Create varsig identity
App->>Var: createIdentity()
Var->>WebAuthn: get()
WebAuthn->>Auth: User verification
Auth-->>WebAuthn: Assertion
WebAuthn-->>Var: Assertion
Var->>Var: encode varsig envelope
Var-->>App: Identity
User->>App: Add entry
App->>DB: db.put()
DB->>Var: signIdentity(payload)
Var->>WebAuthn: get()
WebAuthn->>Auth: User verification
Auth-->>WebAuthn: Assertion
WebAuthn-->>Var: Assertion
Var->>Var: encode varsig envelope
Var-->>DB: Varsig signatureExamples
Svelte demos:
examples/webauthn-todo-demo/- WebAuthn DID (no keystore signing; identity-only). Includes discoverable credential diagnostics,Use Existing Passkey,largeBlobidentity metadata recovery, and local fallback recovery.examples/ed25519-encrypted-keystore-demo/- Ed25519 keystore DID; keystore encrypted at rest with WebAuthn (PRF when available, otherwise largeBlob/hmac-secret).examples/webauthn-varsig-demo/- Varsig provider with passkey signing for each entry. Includes discoverable credential diagnostics,Use Existing Passkey,largeBlobvarsig metadata recovery, and local fallback recovery. Live demo: https://dweb.link/ipfs/bafybeib6tpwiby7pik67ufb3lxpr3j4by2l7r3ov3zzk6hjbzjzgsvckhy
Scripted examples:
examples/ed25519-keystore-did-example.js- Keystore DID flow.examples/encrypted-keystore-example.js- Keystore encryption flow.examples/simple-encryption-integration.js- Keystore + database content encryption.
Mermaid sequences for scripts:
docs/EXAMPLE-SEQUENCES.md
Documentation
docs/ED25519-KEYSTORE-DID.mddocs/WEBAUTHN-ENCRYPTED-KEYSTORE-INTEGRATION.mddocs/WEBAUTHN-DID-AND-ORBITDB-IDENTITY.mddocs/STANDALONE-API-PLAN.mddocs/EXAMPLE-SEQUENCES.mddocs/E2E-TEST-SUMMARY.md
Identity Recovery Summary
Current identity recovery behavior in this repo:
- Discoverable passkeys are the default.
- Discoverable authentication can recover the credential ID used for assertion.
- Discoverable authentication does not reliably re-expose the credential public key.
- Because of that, OrbitDB identity recovery requires metadata persistence.
- The demos now try
largeBlobfirst for identity metadata recovery. - If
largeBlobis not supported or has no metadata, the demos fall back to local browser storage.
Practical implication:
- If you create a passkey on one browser profile and later open the app in a fresh profile, the passkey may still exist in the platform passkey manager, but the app can only reconstruct the OrbitDB identity if it can recover metadata from
largeBlobor some other persisted mapping.
License
MIT. See LICENSE.
