— reactjs, firebase, remix — 1 min read
Tutorial on using Firebase to authorize server side access in a Remix app.
Remix has a good support for server side rendering. In this part I will be using Firebase to authenticate user on server side instead of client side authentication that I did in the previous one.
You need to know how to setup firebase admin sdk I missed out that part while recording but you need to download
service-account.json
We need a Login form where user can enter username and password.
Once we have that we need to validate it using firebase authenticaton.
We do that using signInWithEmailAndPassword. This method returns
idtoken which we pass to Remix to create Cookie.
1// snipped imports and boilerplate2if (process.env.NODE_ENV === "development") {3 connectAuthEmulator(auth, "http://localhost:9099");4}5
6export default function LoginIndex() {7 const [username, setUsername] = useState("");8 const [password, setPassword] = useState("");9 function login() {10 signInWithEmailAndPassword(auth, username, password)11 .then((user: UserCredential) => {12 user.user13 .getIdToken()14 .then((idToken) => {15 return fetch("/session", {16 method: "POST",17 body: JSON.stringify({ idToken }),18 });19 })20 .then(() => {21 // @ts-ignore22 window.location = "/dashboard";23 });24 })25 .catch((err) => console.error({ err }));26 }27
28 return (29 <Layout>30 <h1 className="font-bold text-3xl">Login Form</h1>31 <div className="pb-5">32 <input33 placeholder="email"34 onChange={(e) => setUsername(e.currentTarget.value)}35 />36 </div>37 <div className="pb-5">38 <input39 placeholder="password"40 onChange={(e) => setPassword(e.currentTarget.value)}41 />42 </div>43 <button onClick={login}>Login</button>44 </Layout>45 );46}Next up we have a utility file which we need on Server side to
1const expiresIn = 60 * 60 * 24 * 5 * 1000;2
3export async function createUserSession(idToken: string, cb: Function) {4 return admin5 .auth()6 .createSessionCookie(idToken, { expiresIn })7 .then((sessionCookie) => {8 return cb(sessionCookie);9 });10}11
12export async function getUserSession(request: Request) {13 return request.headers.get("Cookie");14}15
16export async function requireUserId(request: Request) {17 const sessionCookie = (await getUserSession(request)) || "";18 return admin19 .auth()20 .verifySessionCookie(sessionCookie, true)21 .then((decodedClaims) => {22 return decodedClaims;23 })24 .catch((err) => {25 throw redirect("/");26 });27}Session.tsx this Remix Action function takes care of setting Cookie in the response header. This gets invoked once the user is logged in using Firebase Auth.
1import { ActionFunction, json } from "remix";2import { createUserSession } from "~/utils/session.server";3export const action: ActionFunction = async ({ request }) => {4 if (request.method === "POST") {5 const payload = await request.json();6 const { idToken } = payload;7 return createUserSession(idToken, (cookie: string) => {8 return json(9 { idToken },10 {11 headers: {12 "Set-Cookie": cookie,13 },14 }15 );16 });17 }18 return json({ message: "Method not allowed" }, 405);19};Dashboard: Accessible only if session cookie is present Create a Remix Loader function that checks if request scope has session token and then renders the component else it redirects to login screen.
1import { LoaderFunction, useLoaderData } from "remix";2import { requireUserId } from "~/utils/session.server";3
4export const loader: LoaderFunction = async ({ request }) => {5 const decodedClaims = await requireUserId(request);6 return { decodedClaims };7};8
9export default function DashboardIndex() {10 const actionData = useLoaderData();11 return (12 <div className="container mx-auto p-10">13 <h1 className="text-2xl">PersonalDashboard</h1>14 <p>15 Logged in as{" "}16 <span className="bg-red-200 italic ml-2">17 {actionData.decodedClaims.email}18 </span>19 </p>20 <form action="/logout" method="post">21 <button type="submit" className="button">22 Logout23 </button>24 </form>25 </div>26 );27}