A gaming studio can intend to publish a domain proof long before the TXT record is visible to your verifier. Those are different states, and onboarding must not collapse them. Short answer: bind a fresh, exact-name TXT challenge to the intended workspace; check the published value before marking the domain verified; then decide workspace entry separately. A verified domain can make an employee eligible to request access. It should not, by itself, place that employee in a workspace.
This is a build log for that boundary, not a product comparison. The question is whether a SaaS workspace should auto-join someone with a matching email domain or require manual approval after TXT proof. My default for a gaming organization's onboarding flow is approval until the organization explicitly enables auto-join for a narrowly defined domain. The TXT record proves control of a DNS publishing path at verification time. It does not prove that every mailbox under that domain belongs to a person who should see unreleased game assets.
Should verified domain TXT proof auto-join workspace access or require manual approval?
The tempting implementation is one boolean: domainVerified. It fails to express when the challenge was issued, which workspace requested it, which DNS name was queried, or whether the access policy was chosen by an authorized workspace administrator. More importantly, an email suffix match answers a different question from DNS control. An applicant presenting artist@studio.example has supplied a string; they have not demonstrated possession of that mailbox merely by typing it.
I would model three gates: published DNS proof, authenticated email ownership, and the workspace's admission policy. Keep them separate even if the UI presents one onboarding screen. DNS publication can lag behind an administrator's intent; an old resolver view can also lag behind a changed record. Treat a failed lookup or a mismatched TXT value as pending, not as a reason to guess.
No entry yet.
The challenge needs an exact label, such as _workspace-proof.studio.example, rather than a scan for an arbitrary token somewhere under the parent zone. Store the requested domain, workspace ID, challenge digest, issue time, expiry time, and verification state together. The DNS publisher can rotate or remove records. If the record is removed later, the system needs a recheck policy before using that old proof for a new access-policy decision. That policy is an application choice, not a property DNS supplies. Imagine a studio administrator creating a challenge for studio.example, adding the TXT record, and then replacing the record while reorganizing DNS. A pending verification can succeed only against the still-valid token attached to that studio's workspace. A later access request cannot treat an unrelated TXT response or an expired challenge as a shortcut to joining. The system must retain which decision was made from which observed record and which admission policy was active at that point; otherwise a DNS check performed for onboarding becomes an unexplained authorization grant months later.
How would I implement the smallest useful gate?
The verifier below assumes the applicant's email address has already been authenticated by a separate flow. It also assumes a persisted challenge generated with cryptographic randomness and bound to one workspace. The function reads only the expected DNS name; it never infers membership from a successful lookup. Use a DNS client configured for the environment, and compare the complete token, not a substring.
import { resolveTxt } from "node:dns/promises";
import { timingSafeEqual } from "node:crypto";
type Challenge = {
workspaceId: string;
domain: string;
token: string;
expiresAt: number;
};
function equalToken(actual: string, expected: string): boolean {
const a = Buffer.from(actual);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}
async function publishedProof(challenge: Challenge): Promise<boolean> {
if (Date.now() >= challenge.expiresAt) return false;
const name = `_workspace-proof.${challenge.domain}`;
try {
const records = await resolveTxt(name);
return records.some(parts =>
equalToken(parts.join(""), `workspace-proof=${challenge.token}`)
);
} catch {
return false;
}
}
TXT responses can contain multiple records, and a single TXT value can arrive as multiple character strings; joining each record's parts before comparing matters. Do not join all records into one synthetic value. A failed DNS query returns false here to fail closed, but the surrounding job should retain the difference between "not found," "mismatch," and "lookup failed" for retries and observability. Also validate and normalize the domain at challenge creation; do not feed a raw applicant-supplied hostname into this lookup.
After proof succeeds, a transaction can move the challenge into a verified state only if its workspace binding and expiry still match. Then apply admission policy to an authenticated mailbox under the exact approved domain. Manual mode creates a pending request for an existing workspace administrator. Auto-join mode grants a predefined, least-privilege role only when an administrator has enabled it for that domain. Do not quietly inherit auto-join from a parent domain or from another workspace that happens to claim the same suffix. For shared or delegated subdomains, an exact-domain rule is easier to reason about than a broad organizational-domain heuristic.
What would I change at scale?
First, separate DNS polling from the signup request. The caller receives a pending state while a worker retries bounded lookups; a transient resolver failure should not turn into a permanent rejection, and a slow resolver should not hold open the user's login. Record the challenge ID, DNS name, query outcome, verification timestamp, and policy decision as distinct events. Never log the full challenge token. Test with a fake resolver that returns missing, split, duplicate, stale, and mismatched TXT answers, plus an expiry race immediately before the state transition.
Second, measure the right latency. Time from challenge issuance to published proof observed tells you about onboarding friction, but it combines human DNS edits, DNS propagation, and your retry schedule. Time spent inside a resolver call measures something narrower. I would report both rather than claim a single "verification speed" benchmark without a test setup. Polling too aggressively adds queries without making an unpublished record appear; polling too slowly makes a correctly published record feel broken.
The admission policy carries the sharper trade-off. Auto-join reduces the steps between mailbox authentication and a first workspace session, but increases the consequence of a permissive domain rule or a compromised mailbox. Manual approval adds administrator work and waiting time while allowing a human to check whether the applicant belongs on this game's team. For a new studio domain, start with manual approval and make the shift to auto-join an explicit, audited decision.
That wait is real. It buys an explicit access decision.
There is one standards trap worth calling out. DMARC uses DNS-published TXT policy for email authentication and defines organizational-domain handling for that purpose. It does not turn a domain's DMARC record into workspace ownership proof or a membership list. Keep email-authentication policy, your fresh workspace challenge, and the workspace admission rule as three different inputs. If an engineer cannot point to the event that changed each one, the onboarding state machine needs work.
Sources
- RFC 1035, domain names and TXT resource records: https://datatracker.ietf.org/doc/html/rfc1035
- RFC 7489, DMARC and organizational-domain handling: https://datatracker.ietf.org/doc/html/rfc7489
- Node.js DNS promises API,
resolveTxt: https://nodejs.org/api/dns.html#dnspromisesresolvetxthostname - Node.js crypto API,
timingSafeEqual: https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b
Top comments (0)