Skip to content
DomainKit
Esc
navigateopen⌘Jpreview
On this page

Integrate a host application

Place identity, authorization storage, provider credentials, plan attempts, receipts, authenticated endpoints, and React transport around DomainKit.

This guide describes the complete application boundary around DomainKit. The host application owns the security and durability model; DomainKit owns portable connection, planning, authorization, provider, verification, and cleanup semantics.

Architecture

browser or product client
  -> authenticated host endpoints
  -> host identity, tenant policy, and consent checks
  -> durable connection + plan-attempt + receipt storage
  -> domainkit
  -> Cloudflare, Vercel, or another DnsProvider

@domainkit/react uses the same boundary through Transport.Service:

@domainkit/react
  -> Transport.Service
  -> authenticated host client
  -> the server lifecycle above

Provider credentials never cross into the browser transport.

Decide what is durable

State Lifetime Host responsibility
OAuth or integration continuation Short-lived and one-time Store until callback consumption; loss may require restarting consent
Provider authorization Durable Persist account context, capabilities, credential reference, and revocation state
Encrypted credential Durable and secret Encrypt at rest, restrict access, rotate, and audit use
Owner binding and domain grant Durable Bind provider authority to your tenant and permitted domains
Plan attempt and authorization Durable through apply Correlate a server-owned plan with the digest the user reviewed
Apply receipt Durable Preserve confirmed writes and partial outcomes for later cleanup
Public-DNS observation Evidence, not authority Persist only if your product needs history, polling, or readiness state

Do not use a browser store or evictable cache as authoritative storage for provider authorizations, plans, or receipts.

1. Define product requirements on the server

Your product decides which DNS records it needs and why. Decode those requirements at a trusted boundary:

import { DnsRecord } from "domainkit";

const requirement = DnsRecord.parse({
  _tag: "TXT",
  metadata: {
    ownership: "product",
    provenance: "domain-onboarding",
    purpose: "domain-verification",
  },
  name: "_verify.example.com",
  policy: "append",
  ttl: 300,
  value: "verification-value",
});

The browser may display a transport projection of these records, but it must not choose ownership, conflict policy, or provider authority.

2. Implement authorization storage

Supply one AuthorizationLifecycle.Repository for the provider authorization aggregate, encrypted credential reference, and owner bindings. A SQL host can implement the repository as one transaction. A host that splits database and vault storage must implement an idempotent, recoverable saga behind the same interface.

Use domainkit/testing’s in-memory repository in tests only. It does not encrypt credentials or provide production durability.

3. Mount provider connection routes

An interactive provider connection has two server routes:

  1. The start route checks the current user and tenant, creates a short-lived continuation, and returns a provider authorization URL.
  2. The callback route checks the same authority, consumes the continuation exactly once, exchanges the provider code, and commits the durable authorization aggregate.

A token route validates the submitted credential on the server and commits the same durable aggregate without a redirect.

After connection, reconstruct the provider client from the stored credential and non-secret provider context. Do not ask the browser to retain the provider token or account client.

4. Persist plan attempts before approval

When the user asks to review DNS changes:

  1. Load the authorized connection and assert its owner/domain grant.
  2. Reconstruct the provider.
  3. Create the plan against an exact zone or a discovered zone.
  4. Persist the complete encoded plan, digest, connection, tenant, domain, and expiry as one attempt.
  5. Return a browser-safe projection for review.

When the user approves, correlate the submitted digest with that server-owned attempt before calling Provisioning.authorize. Do not rebuild a plan from browser input and treat it as the reviewed artifact.

5. Apply once and persist the receipt

Before apply, claim or otherwise serialize the plan attempt according to your host’s request model. Pass the persisted plan and authorization to Provisioning.apply. Store complete and partial receipts before reporting the outcome.

An apply receipt is not merely UI progress. It is the proof needed to construct a future safe cleanup plan.

6. Map authenticated endpoints into React

Expose only application-facing operations to the browser, then adapt the client once:

import { Transport } from "domainkit";

export const transport = Transport.layerFromAsync({
  connection: api.connections,
  provisioning: api.provisioning,
  verification: api.verification,
  cleanup: api.cleanup,
});

The React transport carries a planDigest; your server can additionally correlate an opaque attempt identifier in its authenticated session, database, or API route. The transport is a browser-safe application contract, not a provider API.

7. Keep product readiness separate

Verification.observe reports provider and public-DNS evidence for DNS requirements. Your product may also need provider-specific service readiness, certificate status, email verification, polling/backoff, or a persisted onboarding phase. Keep that product lifecycle in the host instead of flattening it into DomainKit’s observation result.

Security checklist

  • Provider credentials are accepted and used only on trusted server boundaries.
  • Every connection, reuse, plan, apply, cleanup, and disconnect route rechecks tenant authority.
  • Domain grants are checked in addition to provider scopes.
  • Interactive continuations are single-use and bound to the initiating flow.
  • Plans and cleanup plans are authorized by exact digest.
  • Partial receipts are stored before a retry or remediation path begins.
  • Removing one domain grant does not imply deleting DNS records.
  • Final-binding revocation remains retryable until the provider confirms it.

Next steps

Was this page helpful?