Understanding OTP Authentication with Firebase
What is OTP?
One‑Time Password (OTP) is a short‑lived numeric or alphanumeric code sent to a user's mobile device or email address. It provides an additional security layer, ensuring that the person signing in possesses the registered device.
Why Choose Firebase for OTP?
Firebase offers a ready‑to‑use Phone Authentication API that abstracts the complexities of SMS delivery, token generation, and verification. It integrates seamlessly with Firebase Auth, Cloud Functions, and Firestore, allowing developers to build a secure, scalable OTP flow without managing telecommunication providers directly.
High‑Level Architecture
+-----------------+ +-------------------+ +-------------------+ | Front‑End | <---> | Firebase Auth | <---> | Cloud Function | | (Web / iOS / | | (Phone Provider) | | (Custom Logic) | | Android App) | +-------------------+ +-------------------+ +-----------------+ | ^ | | | | V | V +-------------------+ | Firestore / | | Realtime DB | +-------------------+
- The client initiates the verification request via
signInWithPhoneNumber. - Firebase Auth sends an SMS containing the OTP.
- The user enters the OTP; Firebase verifies it and returns a credential.
- Optional Cloud Functions can enforce custom business rules (e.g., rate limiting, logging).
Understanding this flow helps you decide where to place custom logic and how to secure each interaction.
Setting Up the Firebase Project and Environment
1. Create a Firebase Project
- Visit the Firebase Console.
- Click Add project, follow the wizard, and enable Google Analytics if desired.
- After creation, navigate to Authentication → Sign‑in method and enable Phone.
2. Configure Phone Authentication
- Under the Phone provider, add a reCAPTCHA verifier for web or SafetyNet for Android. Firebase automatically handles reCAPTCHA on web, but you must add the Site Key for custom domains.
3. Install SDKs
Web (JavaScript)
bash npm install firebase@9.22.0
Android (Kotlin)
gradle implementation platform('com.google.firebase:firebase-bom:33.1.0') implementation 'com.google.firebase:firebase-auth-ktx'
iOS (Swift)
bash pod 'Firebase/Auth'
4. Initialize Firebase in Your App
Web Example
import { initializeApp } from "firebase/app";
import { getAuth, RecaptchaVerifier } from "firebase/auth";
const firebaseConfig = { apiKey: "YOUR_API_KEY", authDomain: "YOUR_PROJECT_ID.firebaseapp.com", projectId: "YOUR_PROJECT_ID", storageBucket: "YOUR_PROJECT_ID.appspot.com", messagingSenderId: "SENDER_ID", appId: "APP_ID" };
const app = initializeApp(firebaseConfig); export const auth = getAuth(app); export const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container', {}, auth);
Android Example (Kotlin)
kotlin class MyApplication : Application() { override fun onCreate() { super.onCreate() FirebaseApp.initializeApp(this) } }
5. Optional: Deploy a Cloud Function for Custom Validation
Create a Node.js function that checks request limits:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.validatePhoneRequest = functions.https.onCall(async (data, context) => { const { phoneNumber } = data; const now = admin.firestore.Timestamp.now(); const docRef = admin.firestore().collection('otp_requests').doc(phoneNumber); const doc = await docRef.get(); if (doc.exists && now.seconds - doc.data().lastRequested < 60) { throw new functions.https.HttpsError('resource-exhausted', 'Too many requests. Try again later.'); } await docRef.set({ lastRequested: now }, { merge: true }); return { status: 'ok' }; });
Deploy with: bash firebase deploy --only functions
Setting up the environment correctly ensures a smooth development experience and avoids common pitfalls like missing reCAPTCHA verification or rate‑limiting issues.
Implementing OTP Flow in Front‑end and Back‑end
1. Front‑end: Requesting the Verification Code
Web (React) Example
import React, { useState } from "react";
import { auth, recaptchaVerifier } from "./firebaseConfig";
import { signInWithPhoneNumber } from "firebase/auth";
function PhoneAuth() { const [phone, setPhone] = useState(""); const [otp, setOtp] = useState(""); const [verificationId, setVerificationId] = useState(null);
const sendCode = async () => { const fullPhone = "+1" + phone; // Adjust country code as needed const confirmation = await signInWithPhoneNumber(auth, fullPhone, recaptchaVerifier); setVerificationId(confirmation.verificationId); alert("OTP sent!"); };
const verifyCode = async () => { const cred = PhoneAuthProvider.credential(verificationId, otp); await signInWithCredential(auth, cred); alert("Authentication successful"); };
return ( <div> <input placeholder="Phone number" value={phone} onChange={e => setPhone(e.target.value)} /> <button onClick={sendCode}>Send OTP</button> <input placeholder="Enter OTP" value={otp} onChange={e => setOtp(e.target.value)} /> <button onClick={verifyCode}>Verify OTP</button> <div id="recaptcha-container"></div> </div> ); }
export default PhoneAuth;
2. Front‑end: Android (Kotlin) Example
kotlin class PhoneAuthActivity : AppCompatActivity() { private lateinit var auth: FirebaseAuth private var verificationId: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_phone_auth)
auth = Firebase.auth
btnSend.setOnClickListener { sendVerificationCode() }
btnVerify.setOnClickListener { verifyCode() }
}
private fun sendVerificationCode() {
val phone = "+1" + etPhone.text.toString()
val options = PhoneAuthOptions.newBuilder(auth)
.setPhoneNumber(phone)
.setTimeout(60L, TimeUnit.SECONDS)
.setActivity(this)
.setCallbacks(callbacks)
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
}
private val callbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(credential: PhoneAuthCredential) {
signInWithPhoneAuthCredential(credential)
}
override fun onVerificationFailed(e: FirebaseException) {
Toast.makeText(this@PhoneAuthActivity, "Error: ${e.message}", Toast.LENGTH_LONG).show()
}
override fun onCodeSent(id: String, token: PhoneAuthProvider.ForceResendingToken) {
verificationId = id
Toast.makeText(this@PhoneAuthActivity, "OTP sent", Toast.LENGTH_SHORT).show()
}
}
private fun verifyCode() {
val code = etOtp.text.toString()
val credential = PhoneAuthProvider.getCredential(verificationId!!, code)
signInWithPhoneAuthCredential(credential)
}
private fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) {
auth.signInWithCredential(credential)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
Toast.makeText(this, "Login successful", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Verification failed", Toast.LENGTH_SHORT).show()
}
}
}
}
3. Back‑end: Custom Token Generation (Optional)
If you need to integrate OTP verification with existing user records, generate a custom Firebase token after verifying the OTP on a Cloud Function.
exports.createCustomToken = functions.https.onCall(async (data, context) => {
const uid = data.uid; // e.g., UID from your own DB
const additionalClaims = { phoneVerified: true };
const token = await admin.auth().createCustomToken(uid, additionalClaims);
return { token };
});
The client can then exchange this token for a Firebase session:
const customToken = await firebase.functions().httpsCallable('createCustomToken')({ uid: userId });
auth.signInWithCustomToken(customToken.data.token);
4. Security Best Practices
- Rate limiting - Use Cloud Functions (as shown earlier) to prevent brute‑force attempts.
- reCAPTCHA verification - Mandatory for web to block automated abuse.
- Token expiration - OTP codes are time‑bound (typically 5‑10 minutes). Do not store them beyond verification.
- Audit logging - Write verification attempts to Firestore for forensic analysis.
By following these implementation steps, you can deliver a reliable, secure OTP experience across platforms.
FAQs
Q1: Do I need to pay for SMS messages sent by Firebase?
A: Firebase Phone Authentication uses the underlying carrier's SMS service. While Firebase does not charge per message, you are responsible for any carrier fees incurred by the end‑user. For development, Firebase provides a limited number of free verification codes per project.
Q2: How can I test OTP flow without sending real SMS?
A: Use Firebase's test phone numbers feature. In the Authentication console, add a number (e.g., +1 555‑555‑5555) and specify a static verification code. The SDK will accept this code without contacting a carrier, allowing automated integration tests.
Q3: Can I use email or voice call as an alternative to SMS?
A: Firebase Auth natively supports only SMS for phone verification. However, you can build a custom flow using Cloud Functions and third‑party services (Twilio, MessageBird) to send voice calls or email OTPs, then validate the code manually before creating a Firebase custom token.
Q4: What happens if the user changes their phone number after registration?
A: Prompt the user to re‑verify the new number using the same OTP flow. After successful verification, update the phoneNumber property on the Firebase user record with admin.auth().updateUser(uid, { phoneNumber: newNumber }).
Q5: Is OTP authentication GDPR‑compatible?
A: Yes, provided you disclose the data processing in your privacy policy, store minimal personal data, and allow users to delete their phone information. Firebase stores phone numbers as part of the user profile, which is considered personal data under GDPR.
Conclusion
Implementing OTP authentication with Firebase delivers a robust, scalable, and developer‑friendly solution for verifying users via their mobile devices. By following the setup steps, understanding the underlying architecture, and leveraging the provided code snippets, you can integrate a secure login experience across web, Android, and iOS platforms.
Remember to enforce rate limiting, enable reCAPTCHA (or SafetyNet), and log verification attempts to safeguard against abuse. When custom business rules are required, Cloud Functions offer a flexible extension point without compromising Firebase's managed infrastructure.
Whether you are building a consumer‑facing app or an enterprise portal, Firebase OTP authentication simplifies the complexities of SMS delivery and token management, letting you focus on core product features and user experience.
Start integrating today, monitor usage, and iterate based on analytics-your users will appreciate the seamless and secure sign‑in flow.
