Check signup emails in Firebase Authentication
Firebase Authentication can run a Cloud Function before it creates a user: a blocking function. This guide uses beforeUserCreated to ask Signitize about the email. Blocking functions need Firebase Authentication with Identity Platform (an upgrade in the Firebase console).
1. Store your key as a secret
firebase functions:secrets:set SIGNITIZE_API_KEY
Paste your key when asked. Get a free key if you don't have one.
2. The function
const { createHash } = require('node:crypto')
const { initializeApp } = require('firebase-admin/app')
const { getFirestore } = require('firebase-admin/firestore')
const { defineSecret } = require('firebase-functions/params')
const { beforeUserCreated, HttpsError } = require('firebase-functions/v2/identity')
initializeApp()
const signitizeKey = defineSecret('SIGNITIZE_API_KEY')
async function checkEmail(email) {
try {
const response = await fetch('https://api.signitize.io/v1/check', {
method: 'POST',
headers: {
Authorization: `Bearer ${signitizeKey.value()}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
signal: AbortSignal.timeout(3000),
})
if (response.ok) return { status: 'checked', ...(await response.json()) }
if (response.status === 400) return { status: 'invalid' }
} catch {
// timeout or network error
}
return { status: 'unavailable' }
}
exports.checkSignupEmail = beforeUserCreated({ secrets: [signitizeKey] }, async (event) => {
const email = event.data?.email
if (!email) return // phone and anonymous signups have no email
const check = await checkEmail(email)
if (check.status === 'invalid') throw new HttpsError('invalid-argument', 'Enter a valid email address.')
if (check.status !== 'checked') return // Signitize unavailable: never block a signup
if (check.disposable) throw new HttpsError('invalid-argument', 'Please use a permanent email address.')
if (!check.deliverable) {
throw new HttpsError('invalid-argument', 'This email address cannot receive mail. Please check it for typos.')
}
const id = createHash('sha256').update(check.email).digest('hex')
const doc = getFirestore().collection('signupEmails').doc(id)
const existing = await doc.get()
if (existing.exists && existing.get('email') !== email) {
throw new HttpsError('already-exists', 'You already have an account.')
}
await doc.set({ email, createdAt: new Date() })
})
The signupEmails collection remembers which inbox already signed up, keyed by a hash of the canonical email. The same typed address may try again; a different alias of the same inbox is refused. Keep this collection closed to clients in your Firestore security rules.
3. Deploy and register
firebase deploy --only functions:checkSignupEmail
Then in the Firebase console, open Authentication > Settings > Blocking functions and select checkSignupEmail for Before account creation, if it isn't selected already.
Notes
- Timeouts: Firebase gives blocking functions 7 seconds in total, so the Signitize call uses a 3-second timeout and then lets the signup through.
- Error messages: the client receives the error wrapped by Firebase. Check the error message in your sign-up code and show your own text.
- Failing open: users created while Signitize was unavailable are unchecked. To recheck them, set a custom claim or a Firestore flag and follow Never block a signup.