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 thetoken
from user authentication and saves it in acookie
on the server, using thefirebase-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, "/");
}
}
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);
});
};
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
}
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),
});
}
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}`,
};
}
);
};
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");
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,
});
}
};
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
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 thetoken
from user authentication and saves it in acookie
on the server, using thefirebase-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 theActionFuntion
and pass appropriate properties as formData
// login.jsx - client
const
…
Top comments (1)
See this video for an updated approach using Remix Cookie Package instead of managing the cookies yourself - youtu.be/8GdYy9PWncI