javascriptreactjsfirebasefirebase-authentication

Firebaseauth phone authentication Error (auth/invalid-app-credential) Reactjs


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.

So after alot of trying and searching I found the solution wihch might be helpful for somebody.



                    **SOLVED** 

Firebase auth does not allow requests from the localhost anymore. I needed to run the app(react js in my case) on 127.0.0.1 to be able to request to firebase auth and I already included 127.0.0.1 in the Authorized domains so I was able to request.

export default defineConfig(() => {
return {
    build: {
        outDir: '../API/wwwroot',
        emptyOutDir: true
    },
    server: {
        host: "127.0.0.1" || "localhost",
        port: 3000,
        https: {
            key,
            cert
        },
    },
    plugins: [react()]
}

})

Another thing when I tried to send code on my Ufone(Pakistani network) I got error code 39 but when I tried with my Zong(Pakistani network) number I was able to receive the code.

Hope this help somebody.


Solution

  • So after alot of trying and searching I found the solution wihch might be helpful for somebody.

    Firebase auth does not allow requests from the localhost anymore. I needed to run the app(react js in my case) on 127.0.0.1 to be able to request to firebase auth and I already included 127.0.0.1 in the Authorized domains so I was able to request.

    export default defineConfig(() => {
    return {
        build: {
            outDir: '../API/wwwroot',
            emptyOutDir: true
        },
        server: {
            host: "127.0.0.1" || "localhost",
            port: 3000,
            https: {
                key,
                cert
            },
        },
        plugins: [react()]
    }})
    

    Another thing when I tried to send code on my Ufone(Pakistani network) I got error code 39 but when I tried with my Zong(Pakistani network) number I was able to receive the code.I also updated my question.

    Hope this help somebody.