Skip to content
Chandan Kumar

Remix Firebase authentication on server side

reactjs, firebase, remix1 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.

app/routes/login/Login.tsx
1// snipped imports and boilerplate
2if (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.user
13 .getIdToken()
14 .then((idToken) => {
15 return fetch("/session", {
16 method: "POST",
17 body: JSON.stringify({ idToken }),
18 });
19 })
20 .then(() => {
21 // @ts-ignore
22 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 <input
33 placeholder="email"
34 onChange={(e) => setUsername(e.currentTarget.value)}
35 />
36 </div>
37 <div className="pb-5">
38 <input
39 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

  1. Validate idToken recieved from the browser
  2. Validate any subsequent request with cookie
app/utils/session.server.ts
1const expiresIn = 60 * 60 * 24 * 5 * 1000;
2
3export async function createUserSession(idToken: string, cb: Function) {
4 return admin
5 .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 admin
19 .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.

app/routes/session.tsx
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 Logout
23 </button>
24 </form>
25 </div>
26 );
27}

Comments

Copyleft. WTH
Theme by LekoArts