Skip to main content
RapidDev - Software Development Agency
firebase-tutorial

How to Implement Phone Authentication in Firebase

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.

What you'll learn

  • How to set up RecaptchaVerifier for phone auth
  • How to send and verify SMS codes with signInWithPhoneNumber
  • How to handle phone auth errors and edge cases
  • How to link phone credentials to existing user accounts
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate7 min read15-20 minFirebase JS SDK v9+, Blaze plan required for SMSMarch 2026RapidDev Engineering Team
TL;DR

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

1

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.

typescript
1import { initializeApp } from 'firebase/app'
2import { getAuth, RecaptchaVerifier } from 'firebase/auth'
3
4const app = initializeApp({
5 apiKey: 'YOUR_API_KEY',
6 authDomain: 'YOUR_PROJECT.firebaseapp.com',
7 projectId: 'YOUR_PROJECT_ID',
8})
9
10const auth = getAuth(app)
11
12// Attach invisible reCAPTCHA to the submit button
13const recaptchaVerifier = new RecaptchaVerifier(auth, 'sign-in-button', {
14 size: 'invisible',
15 callback: () => {
16 // reCAPTCHA solved — will proceed with signInWithPhoneNumber
17 },
18})

Expected result: A RecaptchaVerifier instance is attached to the button element, ready to verify the user before sending SMS.

2

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.

typescript
1import { signInWithPhoneNumber } from 'firebase/auth'
2
3async function sendCode(phoneNumber: string) {
4 try {
5 const confirmationResult = await signInWithPhoneNumber(
6 auth,
7 phoneNumber,
8 recaptchaVerifier
9 )
10 // Store confirmationResult to use when verifying the code
11 return confirmationResult
12 } 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 error
19 }
20}

Expected result: The user receives an SMS with a 6-digit verification code, and a ConfirmationResult object is returned for the next step.

3

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.

typescript
1async function verifyCode(
2 confirmationResult: any,
3 code: string
4) {
5 try {
6 const userCredential = await confirmationResult.confirm(code)
7 const user = userCredential.user
8 console.log('Signed in as:', user.uid)
9 console.log('Phone:', user.phoneNumber)
10 return user
11 } 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 error
18 }
19}

Expected result: The user is signed in and the Firebase Auth state updates. The user object includes the verified phoneNumber field.

4

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.

typescript
1import { linkWithPhoneNumber } from 'firebase/auth'
2
3async function linkPhone(phoneNumber: string) {
4 const currentUser = auth.currentUser
5 if (!currentUser) throw new Error('User must be signed in first')
6
7 const confirmationResult = await linkWithPhoneNumber(
8 currentUser,
9 phoneNumber,
10 recaptchaVerifier
11 )
12 // After user enters the code:
13 // const credential = await confirmationResult.confirm(code)
14 return confirmationResult
15}

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.

5

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.

typescript
1import { onAuthStateChanged, connectAuthEmulator } from 'firebase/auth'
2
3// Connect to emulator in development
4if (process.env.NODE_ENV === 'development') {
5 connectAuthEmulator(auth, 'http://127.0.0.1:9099')
6}
7
8// Listen for auth state changes
9onAuthStateChanged(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

phone-auth.ts
1import { initializeApp } from 'firebase/app'
2import {
3 getAuth,
4 RecaptchaVerifier,
5 signInWithPhoneNumber,
6 onAuthStateChanged,
7 connectAuthEmulator,
8 ConfirmationResult,
9} from 'firebase/auth'
10
11const 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})
16
17const auth = getAuth(app)
18
19if (process.env.NODE_ENV === 'development') {
20 connectAuthEmulator(auth, 'http://127.0.0.1:9099')
21}
22
23let confirmationResult: ConfirmationResult | null = null
24
25export function setupRecaptcha(buttonId: string) {
26 return new RecaptchaVerifier(auth, buttonId, {
27 size: 'invisible',
28 })
29}
30
31export async function sendVerificationCode(
32 phoneNumber: string,
33 recaptcha: RecaptchaVerifier
34) {
35 confirmationResult = await signInWithPhoneNumber(auth, phoneNumber, recaptcha)
36 return confirmationResult
37}
38
39export async function confirmCode(code: string) {
40 if (!confirmationResult) throw new Error('Send code first')
41 const credential = await confirmationResult.confirm(code)
42 return credential.user
43}
44
45export 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.

ChatGPT Prompt

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.

Firebase Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.