Skip to content

Sign-in and authentication

Each deployment chooses how people sign in and who may create an account. pnpm dx:deploy asks on the first deploy and records the answers in the signIn block of deploy.selfhost.json. The sign-in page at /signin shows exactly the configured methods, plus links to your Terms of Service and Privacy Policy when you set them.

Method providers value Needs
Magic link magic-link A sender address, authEmailFrom
Email and password email-password authEmailFrom, unless admission is invite
Google google googleClientId and the DX_GOOGLE_CLIENT_SECRET secret

A deployment can offer any combination. With more than one, the sign-in page puts the email field first and the other methods below it. With both magic link and password, a link under the email field switches between them.

Every new password account confirms its address through a verification email before its first session, so public password sign-up needs authEmailFrom. A Google account must have an email Google has verified.

admission decides who may create an account. People who already have one can always sign in.

  • invite (the default): only the administrator and people who open a workspace invite link.
  • waitlist: an unknown address is recorded on the waitlist, and the page tells the person so. It can create an account after you approve it or send it an invite link.
  • open: anyone.

allowedEmailDomains limits new accounts to the listed domains, matched exactly: example.com does not admit team.example.com. An empty list allows any domain. The administrator, approved waitlist addresses, and invite links bypass the waitlist and the domain list.

dx applies these rules before it creates any account, whichever method is used, so Google cannot bypass the waitlist or the domain list.

Sign-in methods share accounts by verified email. The administrator created by the deploy can sign in with Google using the same address and lands in the same account; so can anyone whose account a magic link created. New accounts take their name from the sign-up form or from Google. Google fills in a name a magic-link account never had, but never replaces a name someone already set.

Someone who tries to sign up with an email that already has an account gets an email with a sign-in link instead; the page does not reveal that the account exists.

Google links only to an account whose email is confirmed. A magic link confirms it, and so does the confirmation email a password sign-in sends when the deployment has a sender. The deploy marks the administrator’s address confirmed.

Releases before v0.2.12 stored every self-host account with an unconfirmed email, so Google cannot link to them; the sign-in page says so. The next pnpm dx:deploy confirms the administrator’s address. For other people, either let them confirm it themselves (a magic link, or a password sign-in on a deployment with a sender) or, when you know the address belongs to them, confirm it in the deployment’s D1 database:

Terminal window
pnpm --filter @dx/core exec wrangler d1 execute dx-db-selfhost-<name> --remote \
--command "UPDATE \"user\" SET emailVerified = 1, updatedAt = datetime('now') WHERE email = 'person@example.com'"

Confirm only addresses you trust: on a deployment that allowed open password sign-up without verification, anyone could have registered someone else’s email.

Magic links, verification emails, and notification emails (mentions) come from authEmailFrom through Cloudflare Email Sending. Notification emails need only the sender: they work with any sign-in methods, magic link or not. Without a sender, mentions show in the app only. Email Routing handles inbound mail and is not required.

  1. Choose a sender domain: the deployment zone or one of its subdomains, such as mail.example.com. A dedicated subdomain keeps Cloudflare’s sending records separate from your existing mail records.
  2. In the Cloudflare dashboard for the deployment account, onboard that domain to Email Sending and add the DNS records Cloudflare lists. See Cloudflare’s Email Service documentation and sending-subdomain guide.
  3. Wait until Cloudflare marks the domain enabled.
  4. For magic link, use a Cloudflare API token with the permissions in deployment configuration.

Notification email needs only the sender, which Alchemy’s browser login can deploy. Magic link also needs a Turnstile widget that protects sign-in email requests; the deploy creates and manages it, and only an API token can. With a token the installer checks the exact sender domain before it asks for other secrets. Browser login cannot read Email Sending, so the installer says so and mention emails fail until the domain is onboarded.

  1. In the Google Cloud console, pick or create a project and configure the OAuth consent screen. dx asks only for the openid, email, and profile scopes.
  2. Create an OAuth client ID of type Web application.
  3. Under Authorized redirect URIs, add https://<your-domain>/api/auth/callback/google, for example https://dx.example.com/api/auth/callback/google. The wizard and the deploy output print the exact value. No JavaScript origin is needed.
  4. Copy the client ID and client secret. The client ID is stored in deploy.selfhost.json; the secret is held in deployment state after the first deploy that uses it.

While the consent screen is in testing mode, only the test users you list can sign in with Google. Publish it for everyone else.

The wizard asks, in order:

  1. whether dx sends sign-in email from your domain, and if so the sender address;
  2. which methods to offer: magic link (only when sending email), email and password, Google (client ID and hidden client secret);
  3. who can create an account: open, waitlist, or invite;
  4. allowed email domains (blank allows any);
  5. optional Terms of Service and Privacy Policy URLs.

The administrator password is asked only when email and password is offered.

Rerun the wizard with --sign-in to answer the sign-in questions again:

Terminal window
pnpm dx:deploy -- --sign-in

A deployment created before these settings existed is offered the same step once on its next pnpm dx:deploy. Its earlier behavior is kept if you decline: a sender meant magic link behind the waitlist; otherwise email and password, invite only.

  • Adding Google asks for the client ID and secret in the wizard. To edit the file instead, add google and googleClientId, and set DX_GOOGLE_CLIENT_SECRET in the environment for that run. Rotate the secret later with pnpm dx:deploy -- --rotate.
  • Adding email and password to a deployment that ran without it: the wizard offers to set the administrator password, which attaches to the administrator account that already signed in by magic link or Google.
  • Adding a sender (for notification emails, magic link, or public password sign-up): pnpm dx:deploy offers one once to a deployment that has none, and pnpm dx:deploy -- --email asks again. It needs a custom domain, and works with browser login. Sign-in methods stay as they were, so notification emails start without changing how people sign in; run --sign-in with CLOUDFLARE_API_TOKEN set to also offer magic link.
  • Returning to browser login. A deployment that used an API token only for its sender (any sender set up before 0.2.19) is offered browser login. That deploy uses the token one last time to remove the Turnstile widget and the attachment signing token, which browser login cannot manage; later deploys use browser login, and attachments travel through dx. The choice is recorded as cloudflareAuth (alchemy-oauth or api-token).
{
"authEmailFrom": "sign-in@mail.example.com",
"signIn": {
"providers": ["magic-link", "email-password", "google"],
"admission": "waitlist",
"allowedEmailDomains": ["example.com"],
"googleClientId": "1234567890-abc.apps.googleusercontent.com",
"termsUrl": "https://example.com/terms",
"privacyUrl": "https://example.com/privacy"
}
}
Field Rule
providers One or more of magic-link, email-password, google, each once
admission open, waitlist, or invite; defaults to invite
allowedEmailDomains Lowercase domains, each once; empty allows any
googleClientId Required with, and only with, google
termsUrl, privacyUrl Optional https URLs

allowSignup from earlier releases still works with authEmailFrom. Without a sender, allowSignup: true now fails validation, because public password sign-up verifies each email.

dx has no approval screen yet. Waitlist entries are rows in the deployment’s D1 database, named dx-db-selfhost-<name>. Approve one from the Cloudflare dashboard’s D1 console, or with Wrangler:

Terminal window
pnpm --filter @dx/core exec wrangler d1 execute dx-db-selfhost-<name> --remote \
--command "UPDATE auth_waitlist SET status = 'approved', approved_at = datetime('now'), updated_at = datetime('now') WHERE email = 'person@example.com'"

The person then signs in with any offered method. Sending them a workspace invite link also admits them.

Message Cause
You’re on the waitlist admission is waitlist and the address is not approved
This email domain can’t be used to sign in here The domain is not in allowedEmailDomains
No dx account uses this email admission is invite and there is no invite link
Google hasn’t verified this email address The Google account’s email is unverified
That link is invalid or was already used A magic link or confirmation link was reused or replaced
An account already uses this email … isn’t confirmed yet A Google sign-in matched an account whose email is not confirmed; see accounts created before v0.2.12