Skip to content
Ownfold
Esc
navigateopen⌘Jpreview
On this page

Browser integration

Create a type-safe vault transport, initialize the browser client, persist ciphertext, and handle local recovery flows.

Create a VaultTransport that authenticates each call using the host application’s session. The transport must ignore client-supplied user identity and derive ownership server-side.

Create a browser client with that transport, call initialize(), and render the returned discriminated state. Fallible methods return Promise<Result<T, OwnfoldError>>.

For production browser deployments, configure the optional dedicated-worker engine so root-key bytes and cryptographic execution remain off the UI thread. See Web Worker key isolation for the worker entry, lifecycle, CSP, and failure contract.

Store EncryptedEnvelopeValue through the application’s normal record repository. New writes use V2 while V1 remains readable and locally migratable. Never add plaintext fields as a fallback. On reads, pass the stored envelope and the expected context to decryptJson or decryptBinary.

At the application API boundary, call vaultServer.validateEncryptedRecordWrite(). It accepts only an object containing encryptedPayload, rejects plaintext sibling fields, and verifies the authenticated user’s active vault ID and key version plus the independently supplied namespace, record ID, and owner ID. The returned value is safe to pass to a ciphertext-only repository; the method authenticates metadata but never decrypts record content.

For root-key rotation, implement RotationRecordStore over those encrypted records. Batch reads must return stable checkpoints and absolute progress; batch writes must be atomic and idempotent. Ownfold changes only wrapped data keys and never sends record content to the coordination server.

Recovery Kit creation, download, import, verification, and restoration happen locally. The server stores the active kit identifier and verification status, not Recovery Kit ciphertext or its password.

Standalone replacement changes only the active kit identifier through an optimistic server write; the root key and encrypted records remain unchanged. See Recovery Kits.

Applications may accept a strong user-created password or call RecoveryCode.generate() for a 192-bit browser-generated secret. The generated code must be saved separately: it is intentionally not stored in the Recovery Kit, browser storage, or server metadata.

Operational hooks

The host server owns rate limiting and audit delivery. Configure both when creating the server:

import { RateLimitExceededError } from "@ownfold/core"
import { createVaultServer } from "@ownfold/server"
import { Result } from "better-result"

const vaultServer = createVaultServer({
  adapter,
  getUserId,
  checkRateLimit: async ({ operation, request, userId }) => {
    const allowed = await limiter.check({ operation, request, userId })
    return allowed
      ? Result.ok(undefined)
      : Result.err(
          new RateLimitExceededError({
            code: "RATE_LIMIT_EXCEEDED",
            message: "Too many vault operations. Retry later.",
          }),
        )
  },
  onAuditEvent: (event) => auditSink.enqueue(event),
  onAuditDeliveryError: ({ event }) => metrics.increment("ownfold.audit_delivery_failed", event),
})

Rate-limit checks run after host authentication and before adapter access. A thrown rate-limit hook fails closed with RATE_LIMIT_CHECK_FAILED; fetch maps an explicit limit to HTTP 429 and a limiter outage to HTTP 503. Audit events record authorization decisions, not storage completion. They never contain request bodies, Recovery Kits, passwords, device envelopes, ciphertext, or plaintext. Audit-sink failure does not turn an already allowed or safely rejected operation into an ambiguous client response; onAuditDeliveryError is the host’s alerting path.

Last updated on August 4, 2026

Was this page helpful?