To restrict Firebase Auth signups to specific email domains, use a Blocking Function that runs on the beforeCreate event. The function checks the user's email domain and throws an HttpsError to block registration if the domain is not in your allowlist. This approach works with email/password and OAuth providers. Alternatively, use Firestore security rules to check the email domain on data access instead of at signup time.
Restricting Firebase Auth to Specific Email Domains
Many apps need to limit signups to corporate email domains (for example, only @yourcompany.com). Firebase Auth does not have a built-in domain filter, but you can enforce this with a Blocking Function that intercepts the beforeCreate event. This tutorial shows you how to write the Blocking Function, configure the domain allowlist, handle OAuth providers like Google, and add a fallback check in Firestore security rules.
Prerequisites
- A Firebase project on the Blaze plan
- Firebase Auth with Identity Platform upgrade enabled
- Cloud Functions initialized in your project
- Firebase CLI installed and logged in
Step-by-step guide
Upgrade to Firebase Auth with Identity Platform
Upgrade to Firebase Auth with Identity Platform
Blocking Functions require the Identity Platform upgrade for Firebase Auth. Go to the Firebase Console, navigate to Authentication, and click the Upgrade button on the Identity Platform banner. This upgrade is free for the first 50,000 monthly active users on the Blaze plan. The upgrade adds features like Blocking Functions, multi-tenancy, and SAML/OIDC providers. Once upgraded, you cannot downgrade back to standard Auth.
Expected result: Firebase Auth is upgraded to Identity Platform and Blocking Functions are available.
Create a Blocking Function for beforeCreate
Create a Blocking Function for beforeCreate
Write a Cloud Function that hooks into the beforeCreate event. This function runs every time a new user attempts to register, before the account is created in Firebase Auth. Extract the email from the event, check the domain against your allowlist, and throw an HttpsError with code 'invalid-argument' to block the registration. If the function completes without throwing, the account is created normally.
1import { beforeUserCreated } from 'firebase-functions/v2/identity';2import { HttpsError } from 'firebase-functions/v2/https';3import { logger } from 'firebase-functions/v2';45const ALLOWED_DOMAINS = ['yourcompany.com', 'partner-company.com'];67export const checkEmailDomain = beforeUserCreated(async (event) => {8 const email = event.data.email;910 if (!email) {11 logger.warn('Registration blocked: no email provided');12 throw new HttpsError(13 'invalid-argument',14 'An email address is required to register.'15 );16 }1718 const domain = email.split('@')[1]?.toLowerCase();1920 if (!domain || !ALLOWED_DOMAINS.includes(domain)) {21 logger.warn('Registration blocked: unauthorized domain', {22 email,23 domain24 });25 throw new HttpsError(26 'invalid-argument',27 'Registration is restricted to approved email domains.'28 );29 }3031 logger.info('Registration allowed', { email, domain });32});Expected result: Users with non-approved email domains are blocked from creating accounts. Approved domains are allowed through.
Store the allowlist in a configurable location
Store the allowlist in a configurable location
Hardcoding domains in the function source means you need to redeploy every time the list changes. Instead, store the allowlist in Firestore or in environment variables. Reading from Firestore lets you update the list from the Firebase Console without redeploying functions. Use caching to avoid reading Firestore on every registration attempt.
1import { beforeUserCreated } from 'firebase-functions/v2/identity';2import { HttpsError } from 'firebase-functions/v2/https';3import { getFirestore } from 'firebase-admin/firestore';4import { initializeApp } from 'firebase-admin/app';5import { logger } from 'firebase-functions/v2';67initializeApp();8const db = getFirestore();910let cachedDomains: string[] = [];11let cacheTimestamp = 0;12const CACHE_TTL = 5 * 60 * 1000; // 5 minutes1314async function getAllowedDomains(): Promise<string[]> {15 const now = Date.now();16 if (cachedDomains.length > 0 && now - cacheTimestamp < CACHE_TTL) {17 return cachedDomains;18 }1920 const doc = await db.collection('config').doc('auth').get();21 cachedDomains = doc.data()?.allowedDomains || [];22 cacheTimestamp = now;23 return cachedDomains;24}2526export const checkEmailDomain = beforeUserCreated(async (event) => {27 const email = event.data.email;28 if (!email) {29 throw new HttpsError('invalid-argument', 'Email is required.');30 }3132 const domain = email.split('@')[1]?.toLowerCase();33 const allowedDomains = await getAllowedDomains();3435 if (!domain || !allowedDomains.includes(domain)) {36 logger.warn('Blocked registration', { email, domain });37 throw new HttpsError(38 'invalid-argument',39 'Registration is restricted to approved email domains.'40 );41 }42});Expected result: The domain allowlist is stored in Firestore and can be updated without redeploying Cloud Functions.
Handle the error on the client
Handle the error on the client
When the Blocking Function rejects a registration, the client receives an error from the Firebase Auth SDK. Catch this error and display a user-friendly message. The error code will be functions/invalid-argument (from the HttpsError) and the message will be whatever you set in the Blocking Function.
1import { getAuth, createUserWithEmailAndPassword } from 'firebase/auth';23const auth = getAuth();45async function register(email: string, password: string) {6 try {7 const userCredential = await createUserWithEmailAndPassword(8 auth, email, password9 );10 console.log('Registration successful:', userCredential.user.uid);11 } catch (error: any) {12 if (error.code === 'auth/blocking-function-error-response') {13 // The Blocking Function rejected the registration14 console.error('Domain not allowed:', error.message);15 // Show user-friendly message16 } else if (error.code === 'auth/email-already-in-use') {17 console.error('An account with this email already exists');18 } else {19 console.error('Registration failed:', error.message);20 }21 }22}Expected result: Users see a clear error message when they try to register with an unauthorized email domain.
Add a security rules fallback
Add a security rules fallback
As a defense-in-depth measure, add Firestore security rules that also check the email domain. This protects your data even if someone bypasses the Blocking Function (for example, by creating a user through the Admin SDK). Use request.auth.token.email to access the authenticated user's email in rules.
1rules_version = '2';2service cloud.firestore {3 match /databases/{database}/documents {4 // Helper function to check email domain5 function isApprovedDomain() {6 let email = request.auth.token.email;7 return email.matches('.*@yourcompany\\.com$')8 || email.matches('.*@partner-company\\.com$');9 }1011 match /internal/{docId} {12 allow read, write: if request.auth != null && isApprovedDomain();13 }1415 match /public/{docId} {16 allow read: if request.auth != null;17 allow write: if request.auth != null && isApprovedDomain();18 }19 }20}Expected result: Firestore access is restricted to users with approved email domains, regardless of how their account was created.
Complete working example
1import { beforeUserCreated } from 'firebase-functions/v2/identity';2import { HttpsError } from 'firebase-functions/v2/https';3import { getFirestore } from 'firebase-admin/firestore';4import { initializeApp } from 'firebase-admin/app';5import { logger } from 'firebase-functions/v2';67initializeApp();8const db = getFirestore();910// Cache the allowlist to avoid Firestore reads on every signup11let cachedDomains: string[] = [];12let cacheTimestamp = 0;13const CACHE_TTL = 5 * 60 * 1000; // 5 minutes1415async function getAllowedDomains(): Promise<string[]> {16 const now = Date.now();17 if (cachedDomains.length > 0 && now - cacheTimestamp < CACHE_TTL) {18 return cachedDomains;19 }2021 const doc = await db.collection('config').doc('auth').get();22 const data = doc.data();23 cachedDomains = data?.allowedDomains || [];24 cacheTimestamp = now;2526 logger.info('Refreshed allowed domains cache', {27 count: cachedDomains.length28 });2930 return cachedDomains;31}3233// Block registration for unauthorized email domains34export const checkEmailDomain = beforeUserCreated(async (event) => {35 const email = event.data.email;36 const provider = event.data.providerData?.[0]?.providerId || 'password';3738 if (!email) {39 logger.warn('Registration blocked: no email', { provider });40 throw new HttpsError(41 'invalid-argument',42 'An email address is required to create an account.'43 );44 }4546 const domain = email.split('@')[1]?.toLowerCase();47 const allowedDomains = await getAllowedDomains();4849 if (!domain || !allowedDomains.includes(domain)) {50 logger.warn('Registration blocked: unauthorized domain', {51 email,52 domain,53 provider,54 allowedDomains55 });56 throw new HttpsError(57 'invalid-argument',58 'Registration is restricted to approved organizations. ' +59 'Please use your company email address.'60 );61 }6263 logger.info('Registration approved', {64 email,65 domain,66 provider67 });68});Common mistakes when allowing Only Specific Email Domains in Firebase
Why it's a problem: Trying to use Blocking Functions without upgrading to Identity Platform
How to avoid: Go to Firebase Console > Authentication and upgrade to Identity Platform. Blocking Functions (beforeCreate, beforeSignIn) are only available with the Identity Platform upgrade.
Why it's a problem: Only validating on the client side, which can be easily bypassed
How to avoid: Client-side validation is for UX only. Always enforce domain restrictions server-side with a Blocking Function and in Firestore security rules.
Why it's a problem: Hardcoding the domain list in the function, requiring redeployment for every change
How to avoid: Store the allowlist in Firestore or environment variables so it can be updated without redeploying. Cache the list in memory with a TTL to minimize Firestore reads.
Why it's a problem: Forgetting that OAuth providers (Google) may use different email domains than expected
How to avoid: Google sign-in allows any Gmail address by default. Your Blocking Function intercepts OAuth signups too, so it correctly blocks non-approved domains even for Google sign-in.
Best practices
- Use a Blocking Function on beforeCreate to enforce domain restrictions at the authentication layer
- Store the allowed domains list in Firestore for dynamic updates without redeployment
- Cache the domain list in Cloud Function memory with a short TTL to minimize Firestore reads
- Add Firestore security rules as a defense-in-depth check on email domains
- Handle the blocking error gracefully on the client with a clear user-facing message
- Log blocked and approved registrations for security auditing
- Test the Blocking Function with the Firebase Emulator Suite before deploying to production
- Consider also adding a beforeSignIn Blocking Function to block existing users whose domain was later removed
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Help me restrict Firebase Auth signups to specific email domains like @mycompany.com. Show me how to write a Blocking Function using beforeUserCreated, store the allowed domains in Firestore for easy updates, and handle the error on the client side.
Write a Firebase Cloud Functions v2 Blocking Function that checks the email domain on beforeUserCreated against an allowlist stored in Firestore. Include caching, structured logging, and error handling. Also write the Firestore security rules that enforce the same domain check.
Frequently asked questions
Does this work with Google sign-in and other OAuth providers?
Yes. Blocking Functions intercept all registration methods including email/password, Google, GitHub, and other OAuth providers. The function checks the email regardless of which provider was used.
What happens to existing users if I add this function later?
The beforeCreate Blocking Function only runs on new registrations. Existing users are not affected. To restrict existing users, add a beforeSignIn Blocking Function that checks the email domain on every sign-in.
Is the Identity Platform upgrade free?
The upgrade is free for the first 50,000 monthly active users on the Blaze plan. Beyond that, Identity Platform charges approximately $0.0055 per MAU. Standard Firebase Auth features like email/password and OAuth remain free.
Can I allow multiple email domains?
Yes. Store multiple domains in your allowlist array. The Blocking Function checks if the user's domain is included in the list. You can add or remove domains from the Firestore config document at any time.
What error code does the client receive when blocked?
The client receives the error code auth/blocking-function-error-response. The error message is whatever you set in the HttpsError thrown by the Blocking Function. Catch this specific code to show a domain-restriction message.
Can I use wildcard domains like *.mycompany.com?
Not with simple array includes. Modify the domain check to use string matching: allowedDomains.some(d => domain === d || domain.endsWith('.' + d)). This allows subdomains like team.mycompany.com when mycompany.com is in the list.
Can RapidDev help implement enterprise email domain restrictions?
Yes. RapidDev can set up domain-based access control with Blocking Functions, configurable admin interface for managing approved domains, SAML/OIDC integration for enterprise SSO, and security rules that enforce domain policies across all Firebase services.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation