Checking if a user owns a domain
Paul Walker
Posted on April 3, 2021
The technique we’re using is the one used by Google, Microsoft and others to verify that you've got some authority over a domain. So while it's not foolproof, at least we're in good company!
The code in this article is TypeScript, but the same method would work in most languages.
Overview
All of the verification methods I've seen rely on the user being able to modify the site in some way - which makes sense, since you're checking if they have control over the site they're trying to use.
Most of them seem to have settled on using some form of DNS entry - a special record which they can check actually exists.
Quick DNS intro
This is very brief; for a (slightly) fuller introduction to the DNS, see my other post.
The Domain Name System consists of records giving information to computers accessing the internet. There are quite a few different types of record. The most basic one is called an A record, A for address. It essentially says "this text - foobar.example.com - points to this IP address".
There are a number of reserved addresses which have particular meanings. One useful address is 127.0.0.1
- that always means "this computer". The symbolic name for it is localhost
.
The plan
We want to check the user can modify the DNS entries for that domain, but not with anything particularly disruptive or complicated - the more complicated we make it the more likely it is that user error will creep in.
The simplest way - generate a random subdomain and have them create an A record pointing to 127.0.0.1
.
Generating an alias
There are many different ways to do this. I chose to use the Node uuid module and take the first 8 characters. 8 was chosen because it was random enough for our purposes, and because it was the first 'lump' in the v4 UUID.
siteDetails["alias"] = uuid().substr(0, 8);
Checking the alias
Using the Node dns module we can resolve the alias we created; we append domain
after it, making alias
a subdomain.
The plain dns
methods are callback based; it also supplies a dnsPromises
set of APIs which are Promise based. We’ll use that resolve methodfor convenience.
import dns from "dns";
const dnsPromises = dns.promises;
type Site = {
alias: string; // Alias we'll be verifying
domain: string; // Domain the user gave us
verified: boolean; // Is it verified yet
}
async function verifySite(site: Site) {
try {
const res = await dnsPromises.resolve(site.alias + "." + site.domain);
const valid = ((res.length == 1) && (res[0] == "127.0.0.1"));
site.verified = valid;
} catch (err) {
console.error(`Error ${err} doing site ${site.id} verification`);
}
}
We’re expecting result of the lookup to be a single entry, 127.0.0.1
- if it is then we called it verified. Lastly, we make sure the data reflects what we just found.
Running checks in the background
We now have a function which we can use to verify domains. The last stage is to have it run periodically in the background, rather than on-demand.
The implementation I used is below. I haven’t included the utility functions (like getAllSites
, but the code should still be understandable without those.
startBackground
uses DOMAIN_VERIFY_PERIOD_SECONDS
from the environment if it’s defined - if it isn’t it defaults to 300 seconds (5 minutes). It then uses setInterval
to schedule verifySites
. setInterval
takes milliseconds as an argument, so we convert it first.
verifySites
simply gets the current list of sites and runs verifySite
on all of them.
Lastly, stopBackground
will cancel the interval function if it’s been scheduled to run.
import { getAllSites } from "./queries";
let domainCheckId: NodeJS.Timeout | null = null;
export async function verifySites() {
const sites: Site[] = await getAllSites();
sites.forEach(site => verifySite(site));
}
export function startBackground(): void {
const SECOND = 1000;
const period: number = parseInt(process.env.DOMAIN_VERIFY_PERIOD_SECONDS || "300");
console.log(`Starting domainCheck, period ${period} seconds`);
domainCheckId = setInterval(verifySites, SECOND * period);
}
export function stopBackground(): void {
if (domainCheckId) {
clearInterval(domainCheckId);
domainCheckId = null;
}
}
And that’s it - those functions are enough to start verifying domains in the background. Let me know if you use it!
Posted on April 3, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.