Introduction
This final part completes our authentication system by adding phone/SMS verification. Building on the architecture from Part 1 (Google Auth) and Part 2 (Facebook Auth), we’ll implement secure phone number authentication with OTP verification.
Phone authentication provides :
- Universal accessibility (no social media accounts required).
- High security through device possession.
- Global reach across all countries.
- Two-factor authentication foundation.
Prerequisites
- Completed authentication foundation from Part 1.
- Firebase project with phone authentication enabled.
- Understanding of reCAPTCHA integration.
Firebase Phone Authentication Setup
Step 1 : Enable Phone Authentication
- Firebase Console → Authentication → Sign-in method.
- Click “Phone” → Enable.
- Add authorized domains (localhost + your domain).
Step 2 : Configure Test Phone Numbers (Optional)
For development testing:
- Scroll to “Phone numbers for testing”.
- Add: Phone +917575757575, Code 123456.
- Use these credentials during development.
Update Components
Update Authentication Service
Extend src/services/authService.ts with phone authentication :
// Add this import after other imports
import { signInWithPhoneNumber, RecaptchaVerifier, ConfirmationResult } from 'firebase/auth';
class AuthService {
private recaptchaVerifier: RecaptchaVerifier | null = null;
private confirmationResult: ConfirmationResult | null = null;
// ... existing Google/Facebook methods
// Initialize reCAPTCHA verifier
initializeRecaptcha(containerId: string): void {
if (this.recaptchaVerifier) {
this.recaptchaVerifier.clear();
}
this.recaptchaVerifier = new RecaptchaVerifier(auth, containerId, {
size: 'invisible',
callback: () => {
console.log('reCAPTCHA solved');
},
'expired-callback': () => {
console.log('reCAPTCHA expired');
throw new Error('reCAPTCHA expired, please try again');
}
});
}
// Send OTP to phone number
async sendOTP(phoneNumber: string): Promise {
if (!this.recaptchaVerifier) {
throw new Error('reCAPTCHA not initialized');
}
// Validate phone number format
const phoneRegex = /^\+[1-9]\d{1,14}$/;
if (!phoneRegex.test(phoneNumber)) {
throw new Error('Please enter a valid phone number with country code');
}
try {
this.confirmationResult = await signInWithPhoneNumber(
auth,
phoneNumber,
this.recaptchaVerifier
);
} catch (error: any) {
console.error('Send OTP error:', error);
//You can handle specific errors here
}
// Verify OTP code
async verifyOTP(code: string): Promise {
if (!this.confirmationResult) {
throw new Error('No OTP request found. Please request a new code');
}
// Validate OTP format
if (!/^\d{6}$/.test(code)) {
throw new Error('OTP must be exactly 6 digits');
}
try {
const result = await this.confirmationResult.confirm(code);
this.cleanup(); // Clear after successful verification
return this.formatUser(result.user);
} catch (error: any) {
console.error('Verify OTP error:', error);
}
}
// Cleanup phone auth resources
private cleanup(): void {
if (this.recaptchaVerifier) {
this.recaptchaVerifier.clear();
this.recaptchaVerifier = null;
}
this.confirmationResult = null;
}
// ... rest of existing methods remain same
}
Update Authentication Service
Extend src/services/authService.ts with phone authentication :
// Update AuthContextType interface
interface AuthContextType {
// ... existing properties
sendOTP: (phoneNumber: string) => Promise;
verifyOTP: (code: string) => Promise;
initializeRecaptcha: (containerId: string) => void;
}
// Add to AuthProvider component
export const AuthProvider: React.FC = ({ children }) => {
// ... existing state and effects
const sendOTP = async (phoneNumber: string): Promise => {
try {
setError(null);
await authService.sendOTP(phoneNumber);
} catch (error: any) {
setError(error.message);
throw error;
}
};
const verifyOTP = async (code: string): Promise => {
try {
setError(null);
setLoading(true);
await authService.verifyOTP(code);
} catch (error: any) {
setError(error.message);
throw error;
} finally {
setLoading(false);
}
};
const initializeRecaptcha = (containerId: string): void => {
authService.initializeRecaptcha(containerId);
};
const value = {
// ... existing values
sendOTP,
verifyOTP,
initializeRecaptcha
};
return {children} ;
};
Phone Authentication Component
Create src/components/PhoneAuth.tsx :
// PhoneAuth.tsx
import { useAuth } from "../../contexts/AuthContext";
import authService from "../../services/authService";
// Phone Authentication with OTP using Firebase
const PhoneAuth: React.FC = () => {
const { sendOTP, verifyOTP } = useAuth(); // functions from AuthContext
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [phoneNumber, setPhoneNumber] = useState("");
const [otpCode, setOtpCode] = useState("");
const [step, setStep] = useState<0 | 1>(0); // 0 = enter phone, 1 = enter OTP
useEffect(() => {
// Initialize invisible reCAPTCHA once
authService.initializeRecaptcha("recaptcha-container");
}, []);
// Step 1: Send OTP to the entered phone number
const handleSendOTP = async () => {
if (!/^\+[1-9]\d{1,14}$/.test(phoneNumber)) {
return setError("Enter a valid phone number with country code (e.g., +1234567890)");
}
setLoading(true); setError(null);
try {
await sendOTP(phoneNumber);
setStep(1); // move to OTP step
} catch (e: any) {
setError(e?.message || "Failed to send OTP");
} finally {
setLoading(false);
}
};
// Step 2: Verify the OTP entered by user
const handleVerifyOTP = async () => {
if (otpCode.length !== 6) return setError("OTP must be 6 digits");
setLoading(true); setError(null);
try {
await verifyOTP(otpCode);
// At this point user is authenticated
} catch (e: any) {
setError(e?.message || "Invalid OTP code");
} finally {
setLoading(false);
}
};
return (
{step === 0 ? (
// Phone Number Input
<>
setPhoneNumber(e.target.value)}
disabled={loading}
/>
>
) : (
// OTP Verification Input
<>
Enter the code sent to {phoneNumber}
setOtpCode(e.target.value.replace(/\D/g, "").slice(0, 6))}
disabled={loading}
/>
>
)}
{/* reCAPTCHA container (required by Firebase) */}
);
};
export default PhoneAuth;
Update Authentication Container
Update src/components/auth/AuthenticationContainer.tsx :
// AuthenticationContainer.tsx
//Existing imports..
import PhoneAuth from "./PhoneAuth";
const AuthenticationContainer: React.FC = () => {
return (
//In this after FacebookAuth add Below line
);
};
//Other things remain as it is
These are the primary alterations in your code to instantiate phone authentication logic in your application as it currently exists. You may customize the Dashboard and main App files as per your design, but the code logic should remain as established.
Phone Authentication Key Concepts
- reCAPTCHA Verification :
Firebase requires reCAPTCHA to prevent spam and abuse :
- Invisible : Runs automatically in background.
- Visible : Shows challenge when needed.
- Required : Cannot be disabled for phone auth.
- Two-Step Process
- Send OTP : Validates phone number and sends SMS.
- Verify OTP : Confirms user has access to the device.
- Phone Number Format :
Always use E.164 format : +[country code] [phone number]
- US : +1234567890
- UK : +441234567890
- India : +911234567890
Security Considerations
- Rate Limiting :
Firebase automatically limits OTP requests to prevent abuse :
- Maximum 5 SMS per phone number per hour.
- Exponential backoff for repeated failures.
- Test Phone Numbers
Use Firebase test numbers in development :
// Add to Firebase console for testing
testNumbers: {
'+15555555555': '123456',
'+15555555556': '654321'
}
Production Best Practices :
Monitor Usage : Track SMS costs and usage patterns.
Implement Retry Logic : Handle network failures gracefully.
User Education : Clear instructions for international numbers.
Fallback Options : Provide alternative auth methods.
Common Issues and Solutions
reCAPTCHA Not Loading :
// Ensure proper initialization
useEffect(() => {
const timer = setTimeout(() => {
initializeRecaptcha('recaptcha-container');
}, 1000);
return () => clearTimeout(timer);
}, []);
SMS Not Received :
Check phone number format.
Verify carrier compatibility.
Consider regional restrictions.
Implement resend functionality.
Complete Authentication System :
You now have a comprehensive authentication system with :
Google OAuth : Social login with Google accounts.
Facebook OAuth : Social login with Facebook accounts.
Phone/SMS : Universal authentication via phone numbers.
Unified Architecture : Consistent error handling and user experience.
You can check out the complete code for this part in my GitHub repository here :
https://github.com/Ridham-K/Blog-Code-Examples/tree/main/firebase-auth-demo
Conclusion
The modular architecture created in Part 1 provided for a seamless addition of phone authentication, demonstrating the importance of properly structured authentication services. Your application now offers three of the main authentication methods available while remaining clean and maintainable.
This three-part series has provided a production-ready authentication system able to provide for the differing kinds of requirements of modern web applications.

