Getting Started with Remix: Firebase Email & Google Authentication

Aaron K Saunders - May 31 '22 - - Dev Community

First attempt at integrating firebase with Remix for authentication. I used a combination of Server Token Validation and the client-side API's for authentication.

See this video for an updated approach using Remix Cookie Package

Let me know what you think of this approach, it is still a work in progress as I get a better understanding of the "Remix Way" of doing things.



Firebase Config and How it Works

  • the application uses the firebase client SDK to get the token from user authentication and saves it in a cookie on the server, using the firebase-admin SDK sdk to verify the token in the cookie is still valid
  • add values to the app/firebase-config.json file to support client side API
  • for the server, you will need to download the service account information into a file app/service-account.json

Email And Password Login

Use the client SDK in the ActionFunction for authenticating the user and then pass idToken to a server function to perform the creation of the cookie and Firebase Admin verification of idToken before redirecting to the appropriate path



// in the action function of the component
let formData = await request.formData();
let email = formData.get("email");
let googleLogin = formData.get("google-login");
let password = formData.get("password");

if (googleLogin) {
  // handle google...
} else {
  const authResp = await signInWithEmailAndPassword(auth, email, password);

  // if signin was successful then we have a user
  if (authResp.user) {
    const idToken = await auth.currentUser.getIdToken();
    return await sessionLogin(idToken, "/");
  }
}


Enter fullscreen mode Exit fullscreen mode

Google Login

Since the auth cannot happen on the server so we are doing the login on the client side and then passing the idToken to the server to create the same cookie as we do with an email/password login.

Use the useFetcher hook from Remix to call the ActionFuntion and pass appropriate properties as formData



// login.jsx - client 
const signInWithGoogle = () => {
  const provider = new GoogleAuthProvider();
  signInWithPopup(auth, provider)
    .then(async (res) => {
      const idToken = await res.user.getIdToken();
      fetcher.submit(
        {
          "idToken": idToken,
          "google-login": true,
        },
        { "method": "post" }
      );
    })
    .catch((err) => {
      console.log("signInWithGoogle", err);
    });
};


Enter fullscreen mode Exit fullscreen mode

This snippet of code is from the ActionFunction



// login.jsx - ActionFunction 
let googleLogin = formData.get("google-login");
...
if (googleLogin) {
    return await sessionLogin(formData.get("idToken"), "/");
} else {
    // handle emailPassword login
}


Enter fullscreen mode Exit fullscreen mode

The Server Code

First initialize firebase on the Server Side using the Firebase Admin



// Initialize Firebase
// ---------------------
import * as admin from "firebase-admin";
var serviceAccount = require("./service-account.json");
if (admin.apps.length === 0) {
  admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
  });
}


Enter fullscreen mode Exit fullscreen mode

The main function on the server is the sessionLogin function which basically verifies the token and then creates the cookie using the idToken from the client api.



export const sessionLogin = async (idToken, redirectTo) => {
  return admin
    .auth()
    .createSessionCookie(idToken, {
      expiresIn: 60 * 60 * 24 * 5 * 1000,
    })
    .then(
      (sessionCookie) => {
        // Set cookie policy for session cookie.
        return setCookieAndRedirect(sessionCookie, redirectTo)
      },
      (error) => {
        return {
          error: `sessionLogin error!: ${error.message}`,
        };
      }
    );
};


Enter fullscreen mode Exit fullscreen mode

We also need code to use inside the loader functions of the page components to ensure that we have a valid cookie and if not redirect to login. There is a function called isInvalidSession in the fb.sessions.server.jsx file that we can call to check the session.



// in loader function...
  const { 
    decodedClaims, 
    error 
  } = await isSessionValid(request, "/login");


Enter fullscreen mode Exit fullscreen mode

Here is the code on the server side



export const isSessionValid = async (request, redirectTo) => {
  const cookieHeader = request.headers.get("Cookie");
  const sessionCookie = (await fbSessionCookie.parse(cookieHeader)) || {};
  try {
    const decodedClaims = await admin
      .auth()
      .verifySessionCookie(sessionCookie?.token, true);
    return { success: true, decodedClaims };
  } catch (error) {
    console.log(error);
    // cookie is unavailable or invalid. Force user to login.
    throw redirect(redirectTo, {
      statusText: error?.message,
    });
  }
};


Enter fullscreen mode Exit fullscreen mode

Installing Semantic UI CSS Files and Icons

To get the icons from Semantic UI CSS to work I had to first download all of the files. The copy the assets into the public directory after install. The solutions I found in the discord channel, copying the files from app directory to the build directory, lead me to believe there is no other solution at this time. See package.json for more details

Source Code

GitHub logo aaronksaunders / remix-firebase-sample-app

example app integrating firebase with remix including email and google auth

Welcome to Firebase Remix Example

A sample Remix Application showing account creation, login, logout and forgot password using Firebase


check out video here: https://www.youtube.com/watch?v=ZUVztkkY218


Firebase Config and How it Works

  • the application uses the firebase client SDK to get the token from user authentication and saves it in a cookie on the server, using the firebase-admin SDK sdk to verify the token in the cookie is still valid
  • add values to the app/firebase-config.json file to support client side API
  • for the server, you will need to download the service account information into a file app/service-account.json

Google Login

  • cannot happen on the server so were do the login on the client side and then pass the idToken to the server to create the same cookie as we do with a normal login.
  • use the useFetcher hook to call the ActionFuntion and pass appropriate properties as formData
// login.jsx - client
const
ā€¦
Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .