Search code examples
firebasefirebase-authenticationsveltefirebaseuisveltekit

Sveltekit with Firebase UI Auth difficulties (trouble with v8/v9 and signInSuccessWithAuthResult callback)


I spent way too much time with odd results with Firebase Auth UI. Note: I'm working in Sveltekit, so for the Auth UI I'm using Firebase Auth v8. I know folks are working on the github UI for v9, but at the time of this stackoverflow submission:

https://firebase.google.com/docs/auth/web/firebaseui

Note: FirebaseUI is not compatible with the v9 modular SDK. The v9 compatibility
layer (specifically, the app-compat and auth-compat packages) permits the
usage of FirebaseUI alongside v9, but without the app size reduction and other
benefits of the v9 SDK.

It took me a few false starts to separate sveltekit auth v9 tutorials from the required v8 content for the UI package. Lock into v8 for now.

    import { onMount } from "svelte";
    import { firebaseConfig } from "$lib/firebaseInit";

    import firebase from "firebase/compat/app";
    import * as firebaseui from "firebaseui";
    import "firebaseui/dist/firebaseui.css";

    onMount(async () => {
        await firebase.initializeApp(firebaseConfig);

        // FirebaseUI config.
        var uiConfig = {
            signInSuccessUrl: "", //'<url-to-redirect-to-on-success>',
            callbacks: {
                // Called when the user has been successfully signed in.
                signInSuccessWithAuthResult: function (
                    authResult,
                    redirectUrl
                ) {
                    handleSignedInUser(authResult);
                    return false; // Do not redirect.
                },
            },
            signInOptions: [
                firebase.auth.PhoneAuthProvider.PROVIDER_ID,
            ],
            // Terms of service url/callback.
            tosUrl: "/tos",
            // Privacy policy url/callback.
            privacyPolicyUrl: function () {
                window.location.assign("/privacy_policy");
            },
        };

        // Initialize the FirebaseUI Widget using Firebase.
        let ui = await new firebaseui.auth.AuthUI(firebase.auth());

        /**
         * Displays the UI for a signed in user.
         * @param {!firebase.User} user
         */
        const handleSignedInUser = async function (user) {
            console.log("handleSignedInUser user: ", user);
            document
                .getElementById("user-signed-in")
                .classList.remove("hidden");
            document.getElementById("user-signed-out").classList.add("hidden");
            document.getElementById("phone").textContent =
                "Phone number: " + user.phoneNumber;
        };

        /**
         * Displays the UI for a signed out user.
         */
        const handleSignedOutUser = function () {
            console.log("handleSignedOutUser.... ");
            document.getElementById("user-signed-in").classList.add("hidden");
            document
                .getElementById("user-signed-out")
                .classList.remove("hidden");
            // The start method will wait until the DOM is loaded.
            ui.start("#firebaseui-auth-container", uiConfig);
        };

        const initApp = function () {
            firebase.auth().onAuthStateChanged(
                // auth.onAuthStateChanged(
                function (user) {
                    console.log("initApp .onAuthStateChanged user: ", user);
                    user ? handleSignedInUser(user) : handleSignedOutUser();
                },
                function (error) {
                    console.log(error);
                }
            );
        };

        initApp();

    }); // end of onMount()

    /**
     * Sign out from the user's account.
     */
    function signOutFn() {
        firebase.auth().signOut()
            .then(
                function () {
                    console.log("Signed Out");
                },
                function (error) {
                    console.error("Sign Out Error", error);
                }
            );
    }

    /**
     * Deletes the user's account.
     */
    function deleteAccountFn() {
        firebase.auth().currentUser.delete()
            .catch(function (error) {
                if (error.code == "auth/requires-recent-login") {
                    // The user's credential is too old. She needs to sign in again.
                    firebase.auth().signOut()
                        .then(function () {
                        // The timeout allows the message to be displayed after the
                        // UI has changed to the signed out state.
                            setTimeout(function () {
                                alert(
                                    "Please sign in again to delete your account."
                                );
                            }, 1);
                        });
                }
            });
    }
</script>

<div class="aaa">
    <h2>Authentication stuff goes here...</h2>
    <div id="firebaseui-auth-container" />    
    <div id="user-signed-in" class="bbb hidden" data-role="collapsible">
        <h3>Login Information</h3>
        <div id="user-info" class="bbb">
            <div id="phone" />
        </div>
        <p>
            <button
                id="sign-out"
                on:click={signOutFn}
                class="ui-btn ui-btn-inline">Sign Out</button
            >
            <button
                id="delete-account"
                on:click={deleteAccountFn}
                class="ui-btn ui-btn-inline">Delete account</button
            >
        </p>
    </div>
    <div id="user-signed-out" class="hidden">
        <h4>You are signed out. Please sign in.</h4>
    </div>
</div>

With the code as written, I really struggled on a different matter. If you refreshed the page while previously logged in all displays were fine. If you logged out, then logged back in, the phone number just would not display correctly. I'd get odd errors on my console logs.


Solution

  • So it turns out the uiConfig callback function signInSuccessWithAuthResult(... returns a firebaseui.auth.AuthResult object. And that is NOT a user object.

    user:  User {_delegate: UserImpl, multiFactor: MultiFactorUserImpl}
    

    vs

    authResult: {user: User, credential: null, operationType: 'signIn', additionalUserInfo: GenericAdditionalUserInfo}
    

    The fix... in signInSuccessWithAuthResult callback function:

    Before: handleSignedInUser(authResult);

    After: handleSignedInUser(authResult.user);