Getting Started with Remix: Firebase Email & Google Authentication
Aaron K Saunders
Posted on May 31, 2022
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 componentletformData=awaitrequest.formData();letemail=formData.get("email");letgoogleLogin=formData.get("google-login");letpassword=formData.get("password");if (googleLogin){// handle google...}else{constauthResp=awaitsignInWithEmailAndPassword(auth,email,password);// if signin was successful then we have a userif (authResp.user){constidToken=awaitauth.currentUser.getIdToken();returnawaitsessionLogin(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
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.
exportconstsessionLogin=async (idToken,redirectTo)=>{returnadmin.auth().createSessionCookie(idToken,{expiresIn:60*60*24*5*1000,}).then((sessionCookie)=>{// Set cookie policy for session cookie.returnsetCookieAndRedirect(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.
// in loader function...const{decodedClaims,error}=awaitisSessionValid(request,"/login");
Here is the code on the server side
exportconstisSessionValid=async (request,redirectTo)=>{constcookieHeader=request.headers.get("Cookie");constsessionCookie=(awaitfbSessionCookie.parse(cookieHeader))||{};try{constdecodedClaims=awaitadmin.auth().verifySessionCookie(sessionCookie?.token,true);return{success:true,decodedClaims};}catch (error){console.log(error);// cookie is unavailable or invalid. Force user to login.throwredirect(redirectTo,{statusText:error?.message,});}};
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
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