Firebase phone authentication uses a RecaptchaVerifier to verify the user is human, then sends an SMS code via signInWithPhoneNumber. The user enters the code, and confirmationResult.confirm() completes sign-in. Phone auth requires the Blaze plan because each SMS costs between $0.01 and $0.06 depending on the destination country. Always implement rate limiting and consider linking phone credentials to existing accounts.
Setting Up Phone Authentication in Firebase
Phone authentication lets users sign in by receiving an SMS verification code. This tutorial walks through the complete setup: enabling the phone provider in the Firebase Console, configuring the invisible reCAPTCHA verifier, sending the code, and confirming the user. You will also learn how to handle common errors like invalid phone numbers, expired codes, and rate limits.
Prerequisites
- A Firebase project on the Blaze (pay-as-you-go) plan
- Firebase JS SDK v9+ installed in your project
- Phone sign-in provider enabled in Firebase Console under Authentication > Sign-in method
- Basic understanding of Firebase Auth and async/await
Step-by-step guide
Initialize Firebase and set up RecaptchaVerifier
Initialize Firebase and set up RecaptchaVerifier
Before sending an SMS, Firebase requires a reCAPTCHA challenge to prevent abuse. The invisible RecaptchaVerifier renders automatically when the user clicks the sign-in button, so there is no visible widget. Create the verifier instance and attach it to the sign-in button element. The verifier must be created before calling signInWithPhoneNumber.
1import { initializeApp } from 'firebase/app'2import { getAuth, RecaptchaVerifier } from 'firebase/auth'34const app = initializeApp({5 apiKey: 'YOUR_API_KEY',6 authDomain: 'YOUR_PROJECT.firebaseapp.com',7 projectId: 'YOUR_PROJECT_ID',8})910const auth = getAuth(app)1112// Attach invisible reCAPTCHA to the submit button13const recaptchaVerifier = new RecaptchaVerifier(auth, 'sign-in-button', {14 size: 'invisible',15 callback: () => {16 // reCAPTCHA solved — will proceed with signInWithPhoneNumber17 },18})Expected result: A RecaptchaVerifier instance is attached to the button element, ready to verify the user before sending SMS.
Send the SMS verification code
Send the SMS verification code
Call signInWithPhoneNumber with the user's phone number in E.164 format (for example, +14155552671) and the RecaptchaVerifier. This returns a ConfirmationResult object that you store in state. The user will receive an SMS with a 6-digit code. If the phone number is invalid or the reCAPTCHA fails, the promise rejects with an error.
1import { signInWithPhoneNumber } from 'firebase/auth'23async function sendCode(phoneNumber: string) {4 try {5 const confirmationResult = await signInWithPhoneNumber(6 auth,7 phoneNumber,8 recaptchaVerifier9 )10 // Store confirmationResult to use when verifying the code11 return confirmationResult12 } catch (error: any) {13 if (error.code === 'auth/invalid-phone-number') {14 console.error('Invalid phone number format. Use E.164: +1234567890')15 } else if (error.code === 'auth/too-many-requests') {16 console.error('Too many attempts. Try again later.')17 }18 throw error19 }20}Expected result: The user receives an SMS with a 6-digit verification code, and a ConfirmationResult object is returned for the next step.
Verify the SMS code and complete sign-in
Verify the SMS code and complete sign-in
After the user receives the SMS and enters the 6-digit code, call confirmationResult.confirm() with the code string. On success, this returns a UserCredential with the authenticated user. The verification code expires after a few minutes, so prompt the user to enter it quickly. If the code is wrong, the promise rejects with auth/invalid-verification-code.
1async function verifyCode(2 confirmationResult: any,3 code: string4) {5 try {6 const userCredential = await confirmationResult.confirm(code)7 const user = userCredential.user8 console.log('Signed in as:', user.uid)9 console.log('Phone:', user.phoneNumber)10 return user11 } catch (error: any) {12 if (error.code === 'auth/invalid-verification-code') {13 console.error('Incorrect code. Please try again.')14 } else if (error.code === 'auth/code-expired') {15 console.error('Code expired. Request a new one.')16 }17 throw error18 }19}Expected result: The user is signed in and the Firebase Auth state updates. The user object includes the verified phoneNumber field.
Link phone credentials to an existing account
Link phone credentials to an existing account
If a user is already signed in with email or Google and wants to add their phone number, use linkWithPhoneNumber instead of signInWithPhoneNumber. This links the phone credential to the existing account so the user can sign in with either method in the future. The flow is identical: send code, get ConfirmationResult, confirm the code.
1import { linkWithPhoneNumber } from 'firebase/auth'23async function linkPhone(phoneNumber: string) {4 const currentUser = auth.currentUser5 if (!currentUser) throw new Error('User must be signed in first')67 const confirmationResult = await linkWithPhoneNumber(8 currentUser,9 phoneNumber,10 recaptchaVerifier11 )12 // After user enters the code:13 // const credential = await confirmationResult.confirm(code)14 return confirmationResult15}Expected result: The phone number is linked to the existing user account. The user can now sign in with either their original provider or their phone number.
Handle auth state and test with the emulator
Handle auth state and test with the emulator
Listen for auth state changes with onAuthStateChanged to update your UI after phone sign-in. For local development, use the Firebase Auth emulator to avoid sending real SMS messages and incurring charges. In the emulator, any phone number works and the verification code is logged to the console instead of sent via SMS.
1import { onAuthStateChanged, connectAuthEmulator } from 'firebase/auth'23// Connect to emulator in development4if (process.env.NODE_ENV === 'development') {5 connectAuthEmulator(auth, 'http://127.0.0.1:9099')6}78// Listen for auth state changes9onAuthStateChanged(auth, (user) => {10 if (user) {11 console.log('User signed in:', user.phoneNumber)12 } else {13 console.log('User signed out')14 }15})Expected result: Auth state listener fires when the user signs in via phone. In emulator mode, no real SMS is sent.
Complete working example
1import { initializeApp } from 'firebase/app'2import {3 getAuth,4 RecaptchaVerifier,5 signInWithPhoneNumber,6 onAuthStateChanged,7 connectAuthEmulator,8 ConfirmationResult,9} from 'firebase/auth'1011const app = initializeApp({12 apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY!,13 authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN!,14 projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID!,15})1617const auth = getAuth(app)1819if (process.env.NODE_ENV === 'development') {20 connectAuthEmulator(auth, 'http://127.0.0.1:9099')21}2223let confirmationResult: ConfirmationResult | null = null2425export function setupRecaptcha(buttonId: string) {26 return new RecaptchaVerifier(auth, buttonId, {27 size: 'invisible',28 })29}3031export async function sendVerificationCode(32 phoneNumber: string,33 recaptcha: RecaptchaVerifier34) {35 confirmationResult = await signInWithPhoneNumber(auth, phoneNumber, recaptcha)36 return confirmationResult37}3839export async function confirmCode(code: string) {40 if (!confirmationResult) throw new Error('Send code first')41 const credential = await confirmationResult.confirm(code)42 return credential.user43}4445export function onAuth(callback: (user: any) => void) {46 return onAuthStateChanged(auth, callback)47}Common mistakes when implementing Phone Authentication in Firebase
Why it's a problem: Using phone auth on the free Spark plan, which does not support SMS
How to avoid: Upgrade to the Blaze plan before enabling phone auth. Each SMS costs $0.01-$0.06 depending on the destination country.
Why it's a problem: Creating the RecaptchaVerifier before the DOM element exists
How to avoid: Initialize RecaptchaVerifier inside useEffect (React) or onMounted (Vue) after the button element has rendered.
Why it's a problem: Not handling the auth/too-many-requests error when users repeatedly request codes
How to avoid: Add a cooldown timer (e.g. 60 seconds) between code requests and show the remaining time to the user.
Why it's a problem: Forgetting to store the ConfirmationResult between the send and verify steps
How to avoid: Store the ConfirmationResult in component state or a module-level variable so it is available when the user enters the code.
Best practices
- Always use E.164 phone number format (+countryCodeNumber) to avoid parsing errors
- Add test phone numbers in the Firebase Console for development so you do not incur SMS costs
- Implement a 60-second cooldown between SMS code requests to prevent abuse and billing surprises
- Use the Auth emulator during local development to avoid sending real SMS messages
- Handle all error codes explicitly: invalid-phone-number, too-many-requests, code-expired, invalid-verification-code
- Consider linking phone auth to email or social accounts so users have multiple sign-in options
- Set up budget alerts on your Firebase project to monitor SMS spending
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I need to implement phone number authentication in my Firebase web app using the modular SDK v9. Show me how to set up RecaptchaVerifier, send an SMS verification code with signInWithPhoneNumber, and confirm the code. Include error handling for invalid numbers and expired codes.
Add phone authentication to this Firebase app. Use invisible RecaptchaVerifier, signInWithPhoneNumber for sending codes, and confirmationResult.confirm() for verification. Show a phone number input, a code input, and handle auth/invalid-phone-number and auth/code-expired errors.
Frequently asked questions
Does Firebase phone authentication work on the free Spark plan?
No. Phone authentication requires the Blaze (pay-as-you-go) plan because each SMS verification costs between $0.01 and $0.06 depending on the destination country.
What format should the phone number be in?
Use E.164 international format: a plus sign followed by the country code and phone number with no spaces or dashes. For example, +14155552671 for a US number.
How do I test phone auth without sending real SMS messages?
Use the Firebase Auth emulator for local development, or add test phone numbers with preset verification codes in the Firebase Console under Authentication > Phone > Phone numbers for testing.
How long is the SMS verification code valid?
Verification codes expire after a few minutes. The exact duration is not published, but in practice codes last about 5 minutes. If the user does not enter the code in time, they will need to request a new one.
Can I customize the SMS message text?
No. Firebase controls the SMS message content and format. You cannot customize the message body, though you can set your app's display name in the Firebase Console, which appears in the SMS.
What happens if the user changes their phone number?
You can unlink the old phone provider with user.unlink('phone') and then link the new phone number with linkWithPhoneNumber. The user retains access to their account through other linked providers.
Can RapidDev help set up phone authentication in my Firebase project?
Yes. RapidDev can implement phone authentication with proper error handling, rate limiting, and account linking so your users have a smooth verification experience.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation