Getting Started with Remix: Firebase Email & Google Authentication
Tags: remix, firebase, javascript, react
Published: 2022-05-31 20:29:16

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 

{% youtube 8GdYy9PWncI %}


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.

---
{% youtube ZUVztkkY218 %}

---

## Firebase Config and How it Works

- the application uses the [`firebase client SDK`](https://firebase.google.com/docs/auth/web/manage-users) to get the `token` from user authentication and saves it in a `cookie` on the server, using the [`firebase-admin SDK`](https://firebase.google.com/docs/auth/admin/manage-cookies) 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

```javascript
// 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, "/");
  }
}
```

### 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

```javascript
// 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);
    });
};
```

This snippet of code is from the `ActionFunction`

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

## The Server Code
First initialize firebase on the Server Side using the Firebase Admin
```javascript
// 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),
  });
}
```
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.

```javascript
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}`,
        };
      }
    );
};
```

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.

```javascript
// in loader function...
  const { 
    decodedClaims, 
    error 
  } = await isSessionValid(request, "/login");
```
Here is the code on the server side
```javascript
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,
    });
  }
};
```

## Installing Semantic UI CSS Files and Icons

To get the icons from [Semantic UI CSS](https://semantic-ui.com/introduction/getting-started.html) 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 aaronksaunders/remix-firebase-sample-app %}