Introduction to Firebase OTP Authentication
Why OTP Matters in Modern Apps
One‑time passwords (OTPs) have become a cornerstone of two‑factor authentication (2FA) because they provide a dynamic secret that mitigates credential‑theft attacks. When paired with Firebase Authentication, OTPs can be delivered via SMS or voice call without building a custom messaging infrastructure.
Core Benefits of Using Firebase for OTP
- Scalable infrastructure: Firebase handles quota management, carrier routing, and global delivery.
- Easy SDK integration: Pre‑built client libraries for Android, iOS, and Web reduce boiler‑plate code.
- Built‑in security: Verification tokens are signed with Firebase's private key, preventing tampering.
- Unified user model: OTP users share the same
FirebaseUserobject as email/password users, simplifying downstream logic.
In the sections that follow, we will walk through a production‑ready architecture, present polished code snippets, and outline best practices that keep your OTP flow both usable and secure.
Architecture Overview and Security Considerations
High‑Level Architecture Diagram
+----------------+ +----------------------+ +-------------------+ | Mobile Client | ---> | Firebase Auth API | <--> | Cloud Functions | +----------------+ +----------------------+ +-------------------+ | | | | SMS/Voice Provider | Custom Claims, Rate‑ | | (Twilio, Nexmo, etc.) | Limiting, Auditing | v v v +----------------+ +----------------------+ +-------------------+ | UI Layer | | OTP Generation | | Backend Store | | (React Native,| | (Firebase) | | (Firestore/RTDB) | | Swift, Kotlin)| +----------------------+ +-------------------+ +----------------+
Flow Description
- Phone Number Entry - The client collects a phone number and calls
signInWithPhoneNumber. - OTP Dispatch - Firebase forwards the OTP via the configured SMS/voice provider.
- User Enters OTP - The client verifies the OTP with
PhoneAuthProvider.credential. - Token Issuance - Upon successful verification, Firebase issues a JWT (
idToken). - Optional Backend Validation - Cloud Functions can verify the token, attach custom claims, and enforce additional rate‑limiting.
Security Best Practices
- Enforce Re‑CAPTCHA for web and Android to block automated requests.
- Implement Rate Limiting in Cloud Functions (e.g., allow a maximum of 5 OTP requests per hour per phone number).
- Use Custom Claims to differentiate OTP‑only users from fully verified profiles.
- Store Minimal PII - Only keep the phone number and a timestamp; avoid persisting the OTP itself.
- Leverage Firebase Security Rules to restrict read/write access to the user’s own document.
- Monitor Abuse with Firebase Alerts and Cloud Logging; set up alerts for spikes in
signInWithPhoneNumbercalls.
By adhering to these guidelines, you protect both the user experience and the integrity of your authentication system.
Step‑by‑Step Implementation with Code Samples
1. Setting Up Firebase Project
bash
Install Firebase CLI
npm install -g firebase-tools
Initialize a new project
firebase init
During initialization, enable Authentication (Phone provider) and Functions (Node.js 18 runtime recommended).
2. Client‑Side Integration
Android (Kotlin)
kotlin // Build.gradle (app) implementation "com.google.firebase:firebase-auth-ktx:22.1.2"
kotlin // PhoneAuthActivity.kt private lateinit var callbacks: PhoneAuthProvider.OnVerificationStateChangedCallbacks
private fun startPhoneNumberVerification(phone: String) { val options = PhoneAuthOptions.newBuilder(Firebase.auth) .setPhoneNumber(phone) // E.164 format .setTimeout(60L, TimeUnit.SECONDS) .setActivity(this) .setCallbacks(callbacks) .build() PhoneAuthProvider.verifyPhoneNumber(options) }
init { callbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() { override fun onVerificationCompleted(credential: PhoneAuthCredential) { signInWithPhoneAuthCredential(credential) } override fun onVerificationFailed(e: FirebaseException) { // Handle errors (e.g., invalid number, quota exceeded) } override fun onCodeSent(verificationId: String, token: PhoneAuthProvider.ForceResendingToken) { // Store verificationId for later use } } }
private fun verifyCode(code: String, verificationId: String) { val credential = PhoneAuthProvider.getCredential(verificationId, code) signInWithPhoneAuthCredential(credential) }
private fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) { Firebase.auth.signInWithCredential(credential) .addOnCompleteListener { task -> if (task.isSuccessful) { val user = task.result?.user // Proceed to backend verification if needed } else { // Show error to user } } }
iOS (Swift)
swift import FirebaseAuth
swift func startVerification(phoneNumber: String) { PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate: nil) { verificationID, error in if let error = error { // Handle error return } UserDefaults.standard.set(verificationID, forKey: "authVerificationID") } }
func verifyCode(_ code: String) { guard let verificationID = UserDefaults.standard.string(forKey: "authVerificationID") else { return } let credential = PhoneAuthProvider.provider().credential(withVerificationID: verificationID, verificationCode: code) Auth.auth().signIn(with: credential) { authResult, error in if let error = error { // Show error UI return } // Successful sign‑in, optionally call backend API } }
Web (JavaScript with reCAPTCHA)
<!-- Include Firebase SDK -->
<script src="https://www.gstatic.com/firebasejs/10.4.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/10.4.0/firebase-auth.js"></script>
<div id="recaptcha-container"></div>
// firebase-config.js
import { initializeApp } from "firebase/app";
import { getAuth, RecaptchaVerifier, signInWithPhoneNumber } from "firebase/auth";
const firebaseConfig = {/* your config */}; const app = initializeApp(firebaseConfig); const auth = getAuth(app);
// Initialize reCAPTCHA const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container', {}, auth);
function requestOTP(phoneNumber) { signInWithPhoneNumber(auth, phoneNumber, recaptchaVerifier) .then((confirmationResult) => { window.confirmationResult = confirmationResult; // Store globally for later verification }) .catch((error) => { console.error('OTP request failed', error); }); }
function verifyOTP(code) { window.confirmationResult.confirm(code) .then((result) => { const user = result.user; // User is authenticated - send ID token to backend if needed }) .catch((error) => { console.error('Invalid OTP', error); }); }
3. Backend Validation via Cloud Functions
// functions/src/index.ts
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
admin.initializeApp();
export const verifyIdToken = functions.https.onCall(async (data, context) => {
const idToken = data.token;
try {
const decoded = await admin.auth().verifyIdToken(idToken);
// Attach custom claim if phone is verified but profile incomplete
if (!decoded.email) {
await admin.auth().setCustomUserClaims(decoded.uid, { otpVerified: true });
}
return { uid: decoded.uid, claims: decoded };
} catch (error) {
throw new functions.https.HttpsError('unauthenticated', 'Invalid token');
}
});
Key Points
- Use
verifyIdTokento ensure the JWT came from Firebase. - Apply custom claims to differentiate OTP‑only accounts.
- Store verification timestamps in Firestore for audit trails.
4. Rate Limiting & Abuse Prevention
// functions/src/rateLimit.ts
import * as admin from "firebase-admin";
const db = admin.firestore();
export async function canRequestOTP(phone: string): Promise<boolean> { const docRef = db.collection('otp_requests').doc(phone); const doc = await docRef.get(); const now = admin.firestore.Timestamp.now();
if (!doc.exists) { await docRef.set({ count: 1, lastRequest: now }); return true; }
const data = doc.data()!; const diff = now.seconds - data.lastRequest.seconds; // Reset count after 1 hour if (diff > 3600) { await docRef.set({ count: 1, lastRequest: now }); return true; }
if (data.count >= 5) { return false; // Exceeded limit }
await docRef.update({ count: admin.firestore.FieldValue.increment(1), lastRequest: now }); return true; }
Integrate canRequestOTP before invoking signInWithPhoneNumber on the client (via a callable function) to ensure the user stays within safe thresholds.
By following these snippets, developers can create a robust OTP flow that balances convenience with strong security controls.
FAQs
Frequently Asked Questions
1. Do I need to purchase an SMS gateway when using Firebase OTP?
Firebase partners with global carriers to deliver SMS/voice OTPs at no additional setup cost. However, for high‑volume or region‑specific compliance, you may route messages through a third‑party provider (e.g., Twilio) via Firebase Extensions.
2. Can OTP be used as the sole authentication method?
Yes, but it is recommended to collect additional user data (e.g., email) or to upgrade the account with a password later. Using custom claims (otpVerified) helps you identify accounts that still need enrichment.
3. What happens if a user does not receive the OTP?
Implement a fallback flow that allows the user to request a voice call or to resend the OTP after a short cooldown. Ensure you respect the rate‑limiting rules to avoid carrier throttling.
4. How can I secure my Cloud Functions from abuse?
- Enforce IAM permissions (only authenticated callers).
- Validate the incoming
idTokenbefore processing. - Apply the rate‑limiting logic shown earlier.
- Use
functions.loggerto audit each request.
5. Is reCAPTCHA mandatory for Android?
Firebase automatically triggers SafetyNet reCAPTCHA on Android devices when using signInWithPhoneNumber. You can also configure Play Integrity for an extra layer of protection.
These answers address the most common concerns developers encounter when integrating Firebase OTP authentication.
Conclusion
Wrapping Up
Implementing OTP authentication with Firebase combines the reliability of Google’s backend infrastructure with the flexibility of client‑side SDKs. By respecting the architecture outlined above-starting with a clean phone‑number entry, securing the verification flow with reCAPTCHA, enforcing rate limits, and leveraging Cloud Functions for custom claims-you deliver a frictionless yet highly secure login experience.
Remember to continuously monitor usage patterns, rotate your Firebase service account keys, and keep your SDK versions up to date. When these best practices become part of your development checklist, OTP authentication will not only protect your users but also reinforce trust in your brand.
Start integrating the provided code snippets today, adapt the architecture to your product’s scale, and reap the benefits of a modern, scalable authentication system powered by Firebase.
