The login nobody complains about
A magic link removes the whole category of problem. The client types their email, you email them a one-time sign-in link, they click it, and they're in — no password to create, remember, reset, or leak. There's nothing for them to manage, and crucially, nothing for you to store: no password hashes sitting in your database waiting to be breached. It's the same pattern you've already used a hundred times when a site emails you a 'click here to log in' link.
Why it's quietly more secure (an honest framing)
Be precise about this, though: a magic link is effectively a possession factor (you control the inbox), not a literal TOTP/authenticator-app two-factor setup. It's stronger than a password alone because it removes password reuse, guessing and credential-stuffing as attack paths — but its security rests on the client's email account being secure. If you need true MFA on top, NextAuth supports adding it; the magic link itself is the passwordless base, not a replacement for a hardware key in high-risk contexts.
The stack — boring in the best way
- Next.js (or any framework Auth.js supports) — your app and its API routes.
- NextAuth / Auth.js — the open-source auth library. It ships the passwordless email flow, generates and verifies the one-time sign-in token, and manages the session. (NextAuth and Auth.js are the same project — Auth.js is the framework-agnostic name; in Next.js you still install
next-auth.) - An email provider — to actually send the link. Auth.js has built-in providers for Resend, Nodemailer (any SMTP), Sendgrid, Postmark, Loops and more. Resend is the simplest modern option.
- A database adapter — required for the email/magic-link flow, because Auth.js stores the verification token to validate the link when it's clicked. Any supported adapter (Postgres, MySQL, SQLite, Prisma, Drizzle, etc.) works.
Lock sign-in to one company domain
signIn callback. Return true to allow the sign-in, false to reject it. Restricting to a single domain is one line:profile.email.endsWith("@company.com") for OAuth providers; for the email/magic-link provider the address comes through on the user/email argument. Either way the rule is the same: reject any address that isn't on the allowed company domain. Want multiple clients? Swap the single check for an allow-list of domains.The env-setup checklist (keys never hardcoded)
- Generate the auth secret:
npx auth secret(the documented Auth.js way — it writesAUTH_SECRETinto your.envfor you). Prefer a manual command?openssl rand -base64 33produces an equivalent high-entropy value. - AUTH_SECRET — the value above. Used to sign/encrypt the session. Different value per environment.
- AUTH_RESEND_KEY (or your provider's key) — the email-sending API key, from the Resend dashboard.
- The
fromaddress — a verified sender on a domain you've authenticated in Resend (SPF/DKIM set), so the magic-link emails actually land in the inbox and not in spam. - Database URL — for the adapter that stores verification tokens + sessions.
- Never commit
.env: confirm it's in.gitignore, and set the same keys in your host's encrypted env (Vercel/Cloudflare/Fly env vars) for production.
Ship it in an afternoon — the build order
- 1.
npm i next-auth+ your chosen adapter + the email provider package. - 2. Run
npx auth secretand add your provider key +fromaddress to.env. - 3. Configure the provider (Resend) and the adapter in
auth.ts. - 4. Add the
signIncallback with your company-domain rule. - 5. Drop a 'Sign in with email' form on the page; on submit it triggers the magic-link email.
- 6. Test: a matching-domain address gets the link and gets in; a non-matching one is rejected at the callback.
One honest caveat
Watch the 60-second version
Get the magic-link setup checklist + the next builder breakdown
I'll send the env + stack checklist for this build, plus a new builder concept broken down like this on a regular cadence. No spam, unsubscribe anytime.
By submitting you agree to our Privacy Policy & Terms. Unsubscribe anytime.
Frequently asked questions
Is NextAuth the same thing as Auth.js?
next-auth package; the magic-link email flow, providers and callbacks described here are all part of it.Do I really need a database for magic links?
Is a magic link really 'two-factor authentication'?
How do I restrict sign-in to just one company's domain?
signIn callback and return whether the email ends with the allowed domain — e.g. user.email?.endsWith('@theirCompany.com'). Return false for anything else and that address is rejected. For several clients, check against an allow-list of domains instead of one.How is the AUTH_SECRET generated?
npx auth secret, which generates the value and writes it into your .env. A manual equivalent is openssl rand -base64 33. Use a different secret per environment and keep all of them in env vars — never in committed code.