0

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.

4
  • Have you done a web search for the error code "auth/invalid-app-credential"? It seems to be a common error, and maybe you can learn something from those who have gone before you. Commented Jan 6 at 22:33
  • I tried those solutions but did not work. Commented Jan 6 at 22:42
  • If you don't clearly point out everything you tried in your post along with any specific errors that you encountered with them, then your question might be closed as a duplicate of another post with the same error message. You also might get answers for solutions that are not helpful. I strongly suggest editing the question to be clear that this post is not a duplicate of another. Commented Jan 6 at 23:06
  • @SaimSajid Do not include solution to question please (post a separate answer instead). Commented May 22 at 3:51

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks! just lost 5 hours for nothing

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.