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.
Methods
Section titled “Methods”| Method | providers value |
Needs |
|---|---|---|
| Magic link | magic-link |
A sender address, authEmailFrom |
| Email and password | email-password |
authEmailFrom, unless admission is invite |
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.
Who may create an account
Section titled “Who may create an account”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.
One account per email
Section titled “One account per email”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.
Accounts created before v0.2.12
Section titled “Accounts created before v0.2.12”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:
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.
Send sign-in email
Section titled “Send sign-in 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.
- 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. - 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.
- Wait until Cloudflare marks the domain enabled.
- 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.
Create a Google OAuth client
Section titled “Create a Google OAuth client”- In the Google Cloud console,
pick or create a project and configure the OAuth consent screen. dx asks
only for the
openid,email, andprofilescopes. - Create an OAuth client ID of type Web application.
- Under Authorized redirect URIs, add
https://<your-domain>/api/auth/callback/google, for examplehttps://dx.example.com/api/auth/callback/google. The wizard and the deploy output print the exact value. No JavaScript origin is needed. - 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.
First deploy
Section titled “First deploy”The wizard asks, in order:
- whether dx sends sign-in email from your domain, and if so the sender address;
- which methods to offer: magic link (only when sending email), email and password, Google (client ID and hidden client secret);
- who can create an account:
open,waitlist, orinvite; - allowed email domains (blank allows any);
- optional Terms of Service and Privacy Policy URLs.
The administrator password is asked only when email and password is offered.
Change sign-in on an existing deployment
Section titled “Change sign-in on an existing deployment”Rerun the wizard with --sign-in to answer the sign-in questions again:
pnpm dx:deploy -- --sign-inA 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
googleandgoogleClientId, and setDX_GOOGLE_CLIENT_SECRETin the environment for that run. Rotate the secret later withpnpm 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:deployoffers one once to a deployment that has none, andpnpm dx:deploy -- --emailasks 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-inwithCLOUDFLARE_API_TOKENset 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-oauthorapi-token).
Configuration reference
Section titled “Configuration reference”{ "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.
Approve waitlist entries
Section titled “Approve waitlist entries”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:
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.
Sign-in messages
Section titled “Sign-in messages”| 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 |