Swizec Teller - a geek with a hatswizec.com

    [CodeWithSwiz 21] useAuth beta support for Firebase πŸŽ‰

    It works! useAuth has beta-level support for Firebase Auth πŸ₯³ I did not expect that to take 3 sessions.

    Working Firebase Auth support

    Are you a Firebase user? Can you please try it out and tell me what's missing. It would mean a lot ❀️

    CodeWithSwiz is a weekly live show. Like a podcast with video and fun hacking. Focused on experiments and open source. Join live Mondays

    We got to a working proof of concept in last week's episode. Rough UI, transitions bad, state machines not synced, and we knew it worked because console.log spat out a user.

    Encouraging but not useful.

    This episode was about polishing that turd into a useful beta. Improve the config experience, sync Firebase's and useAuth's state machines, recover sessions on page load, tighten up TypeScript types.

    And as a bonus, we ran into ye olde this problem. Thought those days were behind me πŸ₯²

    Improve the config experience

    You want to reduce moving parts while prototyping. Make sure you can move fast and ignore fiddly details.

    Hardcoding config is a time-honored tradition. A great way to leak your API keys on GitHub and get an AWS account shut down, too.

    Here's how it works now:

    // as a user
    <AuthConfig
    authProvider={FirebaseUI}
    navigate={navigate}
    params={{
    // Firebase standard practice to copypasta this blob?
    firebaseConfig: {
    apiKey: "AIzaSyCdtQ6V3qDxpgDO-usa3zWvBhIJKpAd4mM",
    authDomain: "useauth-demo.firebaseapp.com",
    projectId: "useauth-demo",
    storageBucket: "useauth-demo.appspot.com",
    messagingSenderId: "520315046120",
    appId: "1:520315046120:web:4384141e88f49e638c215d",
    },
    }}
    />

    Reading Firebase docs, it sounds like copy-pasting your firebaseConfig object is standard practice. Go into the dashboard, click a button, paste into your project.

    Firebase Config in dashboard
    Firebase Config in dashboard

    Keeping things familiar so users don't get scared ✌️

    Passing an initialized app

    You might have a Firebase app in your stack already. For authentication or otherwise.

    When you pass firebaseConfig useAuth initializes a new app for itself. That might not be a good idea.

    Instead, you can pass an existing initialized firebase app.

    // as a user
    <AuthConfig
    authProvider={FirebaseUI}
    navigate={navigate}
    params={{
    // pass initialized app
    firebaseApp: Firebase,
    }}
    />

    A conditional in FirebaseUI takes the params object and decides which path to take.

    // src/providers/FirebaseUI.ts
    if (params.firebaseConfig) {
    this.firebase = Firebase.initializeApp(params.firebaseConfig, "useAuth")
    } else if (params.firebaseApp) {
    this.firebase = params.firebaseApp
    } else {
    throw "Please provide firebaseConfig or initialized firebaseApp"
    }

    If there's no config, we throw an error. I hope it's a helpful error ... wish I knew how to encode that in TypeScript. Feels like the type system should enforce conditionally optional params πŸ€”

    Sync Firebase's and useAuth's state machines

    You can think of Firebase and useAuth as 2 state machines running side by side. Firebase keeps track of user state, useAuth wants to keep track of user state.

    useAuth's state machine
    useAuth's state machine

    Syncing state machines is a notoriously difficult problem. You try to figure it out and after 3 days you say "Damn it, why didn't I read a book first?"

    Your best bet is the observer pattern. If the target state machine supports it. Otherwise the actor model works fine.

    You end up with a system like this:

    1. useAuth subscribes to Firebase
    2. User does a thing
    3. Firebase changes state
    4. Firebase tells useAuth
    5. useAuth changes state
    6. useAuth hook updates return values
    7. React re-renders affected components

    Two machines weakly communicating through events, independently changing their internal state. 🀘

    In code you get this:

    // src/providers/FirebaseUI.ts
    constructor(params: AuthOptions & FirebaseOptions) {
    // ...
    // Auth state observer
    this.firebase
    .auth()
    .onAuthStateChanged(this.onAuthStateChanged.bind(this));
    }
    private onAuthStateChanged(user: Firebase.User | null) {
    if (user) {
    this.dispatch("LOGIN");
    this.dispatch("AUTHENTICATED", {
    user: this.firebase.auth().currentUser,
    authResult: {
    // needed for useAuth to work
    expiresIn: 3600
    }
    });
    }
    }

    We subscribe to auth changes with a callback function. The function checks for a user, if one is present, it goes through useAuth events to log you in.

    We dispatch the LOGIN and AUTHENTICATED events because XState doesn't allow jumping ahead. As it shouldn't. But we don't know when this code runs – could be on init before we start logging in.

    Might need to dispatch LOGOUT when there's no user πŸ€”

    Recover sessions on page load

    useAuth has built-in support for validating user sessions on page load. That's like a core value prop.

    To make that happen, it uses a side-effect to call a checkSession function on the auth provider. That method is meant to phone home, check your cookies, whatever it needs to decide "Is the current user still the current user?"

    Firebase makes that part pretty okay:

    public async checkSession(): Promise<{
    user: AuthUser;
    authResult: Auth0DecodedHash;
    }> {
    // verify session is still valid
    // return fresh user info
    const user = this.firebase.auth().currentUser;
    if (user) {
    // throws if user no longer valid
    await user.reload();
    return {
    user,
    authResult: {
    // needed for useAuth to work
    expiresIn: 3600
    }
    };
    } else {
    throw new Error("Session invalid");
    }
    }

    Grab the current user from Firebase, reload their info, return user object and fake an expiresIn timer to keep useAuth happy.

    I considered storing the user.toJSON() version of the user object, but that strips away Firebase methods. Figured people used to Firebase would want to keep those.

    Although the object is gnarly as heck ...

    Swizec Teller published ServerlessHandbook.dev avatarSwizec Teller published ServerlessHandbook.dev@Swizec
    Wow when Firebase returns the authenticated user in a callback ... I guess *some* of those are user properties 🀨
    Tweet media

    The dreaded this

    This part.

    // Auth state observer
    this.firebase.auth().onAuthStateChanged(this.onAuthStateChanged.bind(this))

    Bloody hell I can't remember the last time I had to manually bind a function to its object. Nice reminder that class FirebaseUI {} is syntax sugar for dark magic from 2010.

    Forget to bind and onAuthStateChanged doesn't know which object it belongs to when called as a callback. Throws cryptic errors. πŸ™ƒ

    Fun errors when you forget to bind
    Fun errors when you forget to bind

    πŸŽ‰

    And we got a working beta version.

    Working Firebase Auth support

    But I don't know what's missing. Need to play around more, try different configurations and authentication modes. Figure out how to make developer experience better

    Are you a Firebase user? Can you please try it out and tell me what's missing. It would mean a lot ❀️

    Cheers,
    ~Swizec

    Did you enjoy this article?

    Published on January 26th, 2021 in CodeWithSwiz, Technical, Firebase, useAuth, Livecoding

    Learned something new?
    Want to become a high value JavaScript expert?

    Here's how it works πŸ‘‡

    Leave your email and I'll send you an Interactive Modern JavaScript Cheatsheet πŸ“–right away. After that you'll get thoughtfully written emails every week about React, JavaScript, and your career. Lessons learned over my 20 years in the industry working with companies ranging from tiny startups to Fortune5 behemoths.

    Start with an interactive cheatsheet πŸ“–

    Then get thoughtful letters πŸ’Œ on mindsets, tactics, and technical skills for your career.

    "Man, love your simple writing! Yours is the only email I open from marketers and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. πŸ‘Œ"

    ~ Ashish Kumar

    Join over 10,000 engineers just like you already improving their careers with my letters, workshops, courses, and talks. ✌️

    Have a burning question that you think I can answer?Β I don't have all of the answers, but I have some! Hit me up on twitter or book a 30min ama for in-depth help.

    Ready to Stop copy pasting D3 examples and create data visualizations of your own? Β Learn how to build scalable dataviz components your whole team can understand with React for Data Visualization

    Curious about Serverless and the modern backend? Check out Serverless Handbook, modern backend for the frontend engineer.

    Ready to learn how it all fits together and build a modern webapp from scratch? Learn how to launch a webapp and make your first πŸ’° on the side with ServerlessReact.Dev

    Want to brush up on your modern JavaScript syntax?Β Check out my interactive cheatsheet: es6cheatsheet.com

    By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❀️

    Created bySwizecwith ❀️