Guides
Build guide

Should you run auth on your own domain?

A custom auth domain is close to a one-way door for passkeys, and a DNS record that becomes part of your login path. Here is how to decide, and how to move before anyone signs in.

Dharmendra Jagodana7 min read

In short

Decide before your first user signs in. WebAuthn binds every passkey to the hostname of your auth pages, so moving to a custom domain afterwards leaves existing passkeys inactive. A custom domain also puts a DNS record in your login path, re-checked every 6 hours. The shared platform host never breaks that way.

The advice you will get is that your own domain looks more professional. That is true, and it is not the reason to decide. The reason is that the choice is close to permanent. WebAuthn binds every passkey to the hostname that ran the ceremony, so the domain you sign people in on today is the domain their passkeys belong to. Move it later and those credentials do not come with you.

So the question is not whether auth.example.com reads better than a shared host. It is whether you are willing to own a DNS record that is now part of your login path, and whether you will decide before anyone enrols rather than after.

Key takeaway

If you want a custom auth domain, set it up before your first user signs in. Adding one later leaves every existing passkey inactive, because the relying party ID moves with the hostname. If you are not going to treat that DNS record as production infrastructure, the shared platform host is the better choice, and it cannot break this way.

What you are building

One verified custom domain serving your organization's sign-in pages. When it works, your sign-in page loads from a hostname you own, the console shows the certificate as issued, and every auth flow resolves to that origin instead of the platform default. In the Domains tab the entry carries a Verified badge. The DNS record stays listed underneath it, which is reference rather than an outstanding request.

Before you start

An organization on a plan that includes custom auth domains, DNS access for a domain you own (a subdomain is normal and is what most teams use), and agreement on the hostname you intend to keep. That last one is not padding. It is the whole post. The adjacent decision, how many auth clients to create and which redirect URLs go on each, is its own guide.

Step one: settle the hostname before anyone enrols

The relying party ID is derived from your auth domain and nothing else:

// Derive the WebAuthn relying party from the hosted auth UI base URL.
export function toRelyingParty(authBaseUrl: string): {
  rpID: string;
  origin: string;
} {
  const url = new URL(authBaseUrl);
  return { rpID: url.hostname, origin: url.origin };
}

That base URL is the org's verified custom domain when there is one, and the platform host otherwise. There is no second input, no override, no setting that decouples the two. Change the domain and you change the relying party.

What this looks like to a user is quieter than an error. The browser simply does not offer the passkey, because as far as it is concerned the credential belongs somewhere else. A passkey stores the rpId it was registered under, and when your app lists them each one carries an active flag comparing that stored value against the current auth domain. An inactive passkey is a perfectly good credential for a hostname you walked away from. The enrolment side of all this is in how to add passkeys in Next.js.

So pick the hostname you expect to still be using in three years. If that is the platform host, stop here, and skip to the last section before you spend an afternoon on DNS.

Step two: add the domain

Adding is a POST to the auth domains route, which is where you meet the refusals. Four of them are worth knowing before you hit one:

  • 402 from the plan gate. The feature gate runs first, then the plan's own limit on how many domains you may attach. It answers with the numbers, which is what lets the console draw a usage meter instead of a shrug.
  • 403 at 25 domains, by default. This is an infrastructure ceiling rather than a plan limit. Every domain lands on a shared hosting project, so no plan moves this number, and the refusal deliberately says nothing about upgrading. Offering an upgrade for a ceiling nobody can raise would be a lie.
  • 409 DOMAIN_TAKEN. The registry is global and first-come, and a unique index on the name is what decides the race, so two organizations claiming the same hostname at the same moment cannot both win. If another organization holds the name, it has to be removed there before you can take it. A name your own organization already holds comes back as a 400 instead.
  • 400 for a public suffix. You cannot attach com, co.uk or github.io. Those are not domains anyone owns, and attaching one would be either a mistake or a land grab.

The order matters here. The registry reservation happens before the domain is attached, and it is rolled back if the attach fails, so a refused attach does not normally leave a name squatted on the way out. The rollback is best effort: if it fails too, that is logged rather than retried.

Step three: point DNS, and verify inside 24 hours

The console shows you the record. In the common case it is a CNAME:

CNAME   auth.example.com   cname.vercel-dns.com.

If the platform returns more specific verification records for your domain, the console renders those instead, and you should use what it shows rather than what this guide shows. Add the record, then run verification from the Domains tab.

Warning

A domain that has never verified is deleted 24 hours after you add it. The cleanup runs hourly, removes the domain from the hosting project and the organization, and drops its registry entry, releasing the name. That last part is the one that stings: the name goes back to first-come, so a half-finished setup you left over a long weekend is a name somebody else can take.

Once verification succeeds, the resolver returns your custom origin for the organization and caches that answer for an hour. Sign-in now comes from your hostname. Passkeys registered from this point forward are bound to it.

What breaks later, and how you find out

Verified is not a state you reach and keep. A cron re-checks every registered domain every 6 hours:

registerCron(queue, { pattern: '0 */6 * * *' }, 'domain-revalidation-cron', {
  type: JOB_TYPES.SWEEP,
});

A single negative answer does not count. It schedules re-checks at 5, 15 and 30 minutes, four checks in total, and only the fourth marks the domain unverified. An API error never unverifies anything at all, because "the provider says this domain is not verified" and "we could not reach the provider" are different answers and only one of them is about your DNS.

When the fourth check still says no, the domain is marked unverified, the resolver cache is dropped, and the next auth request falls back to the platform host. Your sign-in page keeps working. Your passkeys do not, because the relying party ID just moved.

Two softenings are built into that, and both exist for the same reason:

The domain is not deleted. Cleanup only removes domains that never verified at all. One that verified once and broke afterwards is kept, unverified, waiting for you, because deleting it would mean re-adding and re-verifying before anyone's passkey worked again.

Recovery needs no human. Fix the record and the next sweep's first positive answer restores the domain on its own.

There is one more piece of defensiveness worth knowing about, because it tells you how seriously the system takes this hostname. If the registry lookup itself fails, the resolver does not fall back to the platform host. It serves the last domain it saw verified, on the grounds that a failed lookup is a bad reason to make everybody's passkeys look stale.

When the shared host is the right answer

Honestly: more often than teams assume.

The shared platform host has no DNS record to misconfigure, no certificate to renew, no revalidation that can flip you back, and no 24-hour window to miss. It is one less piece of infrastructure between a user and a session, and the failure mode described above simply does not exist for it. If your sign-in page is one hop in a flow nobody lingers on, that is a real argument and not a consolation prize.

A custom domain earns its keep when the hostname is doing work: when users would notice being bounced to a name they do not recognise, when cookies need to sit under a domain you control, or when you are selling to people who read the URL bar before they type a password. Those are good reasons. "It looks more professional" is a preference, and preferences are a thin justification for taking on a dependency that can unverify itself at four in the morning.

Either way, decide it once, early, and write the decision down somewhere your future self will look. The expensive version of this is not choosing wrong. It is choosing twice.

npm i @buildbase/sdk
authentication
passkeys
dns

Frequently Asked Questions

Can I add a custom auth domain later, after launch?

Yes, and it costs you something. The relying party ID for passkeys is the hostname of your auth pages, so every passkey registered on the old host stops being offered once you move. Those credentials are not corrupt, they just belong to a domain you stopped using. If you think you will ever want your own domain, set it up before anyone enrols.

What DNS record does a custom auth domain need?

A CNAME pointing the hostname at cname.vercel-dns.com., unless the platform returns more specific verification records for your domain, in which case the console shows those instead. Add the record, then run verification from the Domains tab. An unverified domain is cleaned up after 24 hours and its name is released.

What happens if my DNS breaks after the domain is verified?

A cron re-checks every registered domain every 6 hours. One negative answer is not enough: it schedules re-checks at 5, 15 and 30 minutes, four checks in all, and only the last marks the domain unverified. Sign-in then falls back to the platform host and passkeys registered on the custom domain go inactive. Fix the DNS and one positive answer restores it automatically.

How many custom auth domains can one organization attach?

Availability is a plan feature, and the plan ceiling is applied first with a 402 that carries the numbers. Above that sits an infrastructure ceiling, 25 per organization by default, which answers 403 and deliberately says nothing about upgrading, because no plan moves it.

Ship it

Create a project and run this guide against your own workspace.

7-day free trialNo credit card requiredCancel anytime