Integration guide · Auth.js
How to block disposable emails in Auth.js (NextAuth)
The signIn callback lets you deny a sign-in before Auth.js creates the account. Use it to keep throwaway and dead-domain emails out, including magic links before they are sent.
By the Vetto team · Updated
Quick answer
In the signIn callback, read user.email ?? profile?.email, ask a disposable-email check for a verdict, and return a redirect to your signup page with an error when it says “block”. For magic links, only check when email.verificationRequest is true.
1. Add the check
// lib/vetto.ts
export async function isDisposable(email: string): Promise<boolean> {
try {
const res = await fetch('https://usevetto.com/v1/check', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VETTO_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
signal: AbortSignal.timeout(2500),
});
const v = await res.json();
return v.verdict === 'block';
} catch {
return false; // timeout or network error: fail open
}
}2. Call it from the signIn callback
// auth.ts (Auth.js v5)
import NextAuth from 'next-auth';
import { isDisposable } from './lib/vetto';
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [/* Google, GitHub, Resend (magic link)... */],
callbacks: {
async signIn({ user, profile, email }) {
const address = user.email ?? profile?.email;
if (!address) return true;
// Magic links: the callback runs twice. Check on the first call
// (verificationRequest), before the email is sent.
if (email && !email.verificationRequest) return true;
if (await isDisposable(address)) return '/signup?error=disposable';
return true;
},
},
});profile is only set for OAuth providers, which is why the example falls back to user.email. The official restricting access guide uses the same callback to allow only some domains; this is the same idea in reverse.
What the check catches
- Throwaway inboxes (mailinator.com, yopmail.com and 9,000+ more) and domains on known throwaway mail servers.
- Dead domains with no mail server, so magic links that could never arrive are not sent.
- Repeat signups from one Gmail inbox through dots, +tags or googlemail.com.
- Privacy relays like Apple Hide My Email are allowed.
Related: Supabase · Firebase · how to detect fake signups · free trial abuse.
Frequently asked questions
How do I block disposable emails in NextAuth?
Use the signIn callback. Check the address and return false (or an error-page URL) to deny the sign-in. For magic links, check when email.verificationRequest is true, so the email is never sent.
Does the signIn callback run for every login?
Yes, it runs on every sign-in, not only for new users, and Auth.js has no reliable "is new user" flag there. Checking returning users too is usually fine; if you want to skip them, look the user up in your database first.
What is the difference between returning false and a URL?
false sends the user to the Auth.js error page with AccessDenied. A string redirects to that URL, which lets you show your own message on your signup page.
Does this work with NextAuth v4?
Yes. v4 uses the same signIn({ user, account, profile, email, credentials }) callback with the same return values.
Give your signup form sharp eyes.
Free on one site. One line of HTML. Remove it any time and your form works exactly as before.
Founding offer: Pro for life for $69 once, first 100 buyers, until Nov 27.