Block disposable email signups with Auth.js / NextAuth
NextAuth's `signIn` callback runs before Auth.js writes the account. Return `false` (or a redirect URL) and the signup is refused.
The code
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import Google from 'next-auth/providers/google';
async function isDisposable(email: string) {
try {
const r = await fetch(
`https://api.checkdisposable.email/v1/check?email=${encodeURIComponent(email)}`,
{ headers: { Authorization: `Bearer ${process.env.CDE_KEY!}` } }
);
if (!r.ok) return false;
return (await r.json()).is_disposable === true;
} catch {
return false;
}
}
const handler = NextAuth({
providers: [Google({ clientId: process.env.GOOGLE_ID!, clientSecret: process.env.GOOGLE_SECRET! })],
callbacks: {
async signIn({ user }) {
if (user.email && (await isDisposable(user.email))) {
// Return a URL to redirect there with an error param,
// or just `return false` to bounce with a generic message.
return '/signin?error=DisposableEmail';
}
return true;
},
},
});
export { handler as GET, handler as POST };Notes
- Where the error surfaces
- Returning false sends the user to the default NextAuth error page. Returning a URL gives you control over the message — read the `error` query param on your signin page and render it.
- Email provider gotcha
- If you use the Email (magic link) provider, signIn fires AFTER the verification email is sent. To block before sending, also call isDisposable in `sendVerificationRequest`.
- Account linking
- If a user with the same email already exists, NextAuth links the new provider to the existing account. signIn still fires, so disposable check still runs — good.
Get a free API key
500 checks/month, no credit card. No credit card. 30 seconds.
Sign up free →