I want to authenticate/verify user phone number by sending OTP using firebase auth.
Getting the error:
“Error sending verification code: FirebaseError: Firebase: Error (auth/invalid-app-credential)”
My App configurations match to the one’s in the console and authentication works with the phone number provided in the testing section but failing when I try to send to my personal number.
Number is well formtted like +countrycode+number(+92334*******) and my payment plan is Blaze(Pay as you go).
code
Firebase.ts:
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
const firebaseConfig = {
.......
};
// Initialize Firebase App
const app = initializeApp(firebaseConfig);
// Export Firebase Auth instance
export const auth = getAuth(app);
firebaseUtils.ts:
import { RecaptchaVerifier, signInWithPhoneNumber, PhoneAuthProvider, signInWithCredential } from "firebase/auth";
import { auth } from "../app/firebase/firebaseConfig";
export const initializeRecaptcha = () => {
if (!window.recaptchaVerifier) {
window.recaptchaVerifier = new RecaptchaVerifier(
auth,
"recaptcha-container",
{
size: "normal", // Use 'invisible' for a hidden reCAPTCHA
callback: (response: any) => {
console.log("reCAPTCHA solved:", response);
},
"expired-callback": () => {
console.error("reCAPTCHA expired. Please try again.");
},
}
);
}
return window.recaptchaVerifier;
};
export const sendCode = async (phoneNumber: string) => {
debugger
if (!window.recaptchaVerifier) initializeRecaptcha();
try {
if (!window.recaptchaVerifier) {
throw new Error("ReCAPTCHA verifier not initialized");
}
const confirmationResult = await signInWithPhoneNumber(auth, phoneNumber, window.recaptchaVerifier!);
//.then((confirmationResult) => {
// debugger
// window.confirmationResult = confirmationResult;
// Toast.show("success", "OTP sent successfully");
//})
//.catch((error) => {
// debugger
// console.log(error);
//});
return confirmationResult;
} catch (error) {
console.error("Error sending verification code:", error);
throw error;
}
};
export const verifyCode = async (verificationId: string, verificationCode: string) => {
const credential = PhoneAuthProvider.credential(verificationId, verificationCode);
return await signInWithCredential(auth, credential);
};
VerifyToken.ts:
import { useEffect, useRef, useState } from "react";
import { Formik, ErrorMessage } from "formik";
import { Button, Form, Header, Label, Segment } from "semantic-ui-react";
import * as Yup from "yup";
import MyTextInput from "../../app/common/form/MyTextInput";
import Toast from "../../utils/Toast";
import { initializeRecaptcha, sendCode, /*validateRecaptchaToken,*/ verifyCode } from "../../utils/firebaseUtils";
interface Props {
email?: string; // Optional: for email verification
phoneNumber?: string; // Optional: for phone number verification
onSuccess?: () => void; // Callback after successful verification
}
export default function VerifyToken({ email, phoneNumber, onSuccess }: Props) {
const [timer, setTimer] = useState(120); // 2-minute countdown timer
const [isTimerActive, setIsTimerActive] = useState(false);
const [verificationId, setVerificationId] = useState<string | null>(null);
const hasRun = useRef(false);
useEffect(() => {
if (phoneNumber && !hasRun.current) {
handleCodeRequest();
hasRun.current = true;
}
}, [phoneNumber]);
useEffect(() => {
let countdown: NodeJS.Timeout;
if (isTimerActive && timer > 0) {
countdown = setInterval(() => setTimer((prev) => prev - 1), 1000);
} else if (timer <= 0) {
setIsTimerActive(false);
setTimer(120);
}
return () => clearInterval(countdown);
}, [isTimerActive, timer]);
const handleCodeRequest = async () => {
if (email) {
Toast.show("info", `A code has been sent to your email: ${maskEmail(email)}`);
} else if (phoneNumber) {
try {
debugger
// Initialize reCAPTCHA
const recaptchaVerifier = initializeRecaptcha();
// Wait for reCAPTCHA to be solved
const recaptchaToken = await recaptchaVerifier.verify();
//await validateRecaptchaToken(recaptchaToken)
console.log("reCAPTCHA solved, token:", recaptchaToken);
debugger
// Send the verification code after solving reCAPTCHA
const result = await sendCode(phoneNumber);
if (result) {
setVerificationId(result.verificationId);
Toast.show("info", `A verification code was sent to ${phoneNumber}.`);
setIsTimerActive(true);
}
} catch (error) {
console.error("Error sending verification code:", error);
Toast.show("error", "Failed to send verification code. Please try again.");
}
}
};
const handleSubmit = async (code: string, setErrors: (errors: { error: string }) => void) => {
if (email) {
Toast.show("success", "Email verification successful!");
onSuccess && onSuccess();
return;
}
if (!verificationId) {
setErrors({ error: "Verification ID not available. Please resend the code." });
return;
}
try {
const userCredential = await verifyCode(verificationId, code);
if (userCredential) {
Toast.show("success", "Phone number verified successfully!");
onSuccess && onSuccess();
} else {
throw new Error("Verification failed.");
}
} catch (error: any) {
console.error("Verification error:", error.message);
setErrors({ error: "Invalid code. Please try again or resend." });
}
};
const formatTime = () => {
const minutes = Math.floor(timer / 60);
const seconds = timer % 60;
return `${minutes}:${seconds < 10 ? `0${seconds}` : seconds}`;
};
const maskEmail = (email: string | null) => {
if (!email) return "";
const [localPart, domain] = email.split("@");
if (localPart.length <= 2) return email;
return `${localPart[0]}${"*".repeat(localPart.length - 2)}${localPart.slice(-1)}@${domain}`;
};
return (
<Segment padded style={{ maxWidth: 400, margin: "auto", borderRadius: "15px", background: "#f0f5ff" }}>
<Header as="h2" content={`Verify Your ${email ? "Email" : "Phone Number"}`} color="black" />
<p>Enter the code we sent to <strong>{email ? maskEmail(email) : phoneNumber}</strong>.</p>
{phoneNumber && <div id="recaptcha-container" style={{ marginBottom: "10px" }}></div>}
<Formik
initialValues={{ code: "", error: "" }}
validationSchema={Yup.object({ code: Yup.string().required("Code is required") })}
onSubmit={(values, { setErrors }) => handleSubmit(values.code, setErrors).catch((error) => setErrors({ error: error.message }))}
>
{({ handleSubmit, errors, isValid, isSubmitting }) => (
<Form className="ui form" onSubmit={handleSubmit} autoComplete="off">
<MyTextInput placeholder="Enter code" name="code" type="text" />
<ErrorMessage name="error" render={() => <Label style={{ marginBottom: 10 }} basic color="red" content={errors.error} />} />
<div style={{ display: "flex", alignItems: "center", marginBottom: "20px", cursor: isTimerActive ? "not-allowed" : "pointer", color: isTimerActive ? "gray" : "#4a90e2" }} onClick={!isTimerActive ? handleCodeRequest : undefined}>
{isTimerActive ? <span>Try again in {formatTime()}</span> : <span>Resend Code</span>}
</div>
<Button disabled={!isValid} loading={isSubmitting} primary content="Verify" type="submit" fluid />
</Form>
)}
</Formik>
</Segment>
);
}
Request that fail:
Request URL:
https://identitytoolkit.googleapis.com/v1/accounts:sendVerificationCode?key=key
Request Method:
POST
Status Code:
400 Bad Request
Remote Address:
address
Referrer Policy:
no-referrer
payload:
{
“phoneNumber”: “+9231********”,
“clientType”: “CLIENT_TYPE_WEB”,
“captchaResponse”: “NO_RECAPTCHA”,
“recaptchaVersion”: “RECAPTCHA_ENTERPRISE”,
“recaptchaToken”: “token”
}
Response:
{
“error”: {
“code”: 400,
“message”: “INVALID_APP_CREDENTIAL”,
“errors”: [
{
“message”: “INVALID_APP_CREDENTIAL”,
“domain”: “global”,
“reason”: “invalid”
}
]
}
}
Is this failing becuase of RECAPTCHA_ENTERPRISE(also tried using RECAPTCHA_ENTERPRISE in the previous firebase project but didn’t work and didn’t enable it in new firebase project)?
I also added the 127.0.0.1 in firebase authentication setting under Authorized domains.
But It does not allow 127.0.0.1:3000.