with React Router v7, Firebase, and Remix-Utils
Server-side authentication is critical for securely managing user sessions in modern web applications. In this in-depth guide, you'll learn how to implement robust server-side authentication using React Router v7, Firebase Authentication, and Remix-Utils for added security measures such as CSRF protection.
Before starting, ensure your project has the necessary dependencies:
npm install firebase remix-utils react-router drizzle-orm
Create a Firebase initialization file:
// lib/firebase/firebase.server.ts
import admin from "firebase-admin";
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert({ /* your Firebase credentials */ }),
});
}
export const serverAuth = admin.auth();
Create your login layout using React Router's structured routes:
// routes/sign-in/page.tsx
import { Outlet } from "react-router";
export default function LoginPage() {
return (
<div className="login-container">
<Outlet />
</div>
);
}
Create a wrapper layout that ensures all private routes are protected:
// routes/navigation/nav-layout.tsx
import { Outlet, redirect } from "react-router";
import { getUserDataSession } from "~/lib/get-user-data-session";
export async function loader({ request }) {
const { userData } = await getUserDataSession(request);
if (!userData) {
return redirect("/sign-in");
}
return { userData };
}
export default function NavLayout({ loaderData }) {
return (
<div>
<nav>User: {loaderData.userData.email}</nav>
<Outlet />
</div>
);
}
Implement an authentication loader to verify user sessions:
// lib/get-user-data-session.ts
import { getSession } from "~/lib/session/session.server";
import { serverAuth } from "~/lib/firebase/firebase.server";
export async function getUserDataSession(request: Request) {
const session = await getSession(request.headers.get("Cookie"));
const sessionCookie = session.get("sessionCookie");
if (sessionCookie) {
try {
const userData = await serverAuth.verifySessionCookie(sessionCookie, true);
return { session, userData };
} catch {
session.unset("sessionCookie");
}
}
return { session, userData: null };
}
Implement CSRF protection using Remix-Utils:
// utils/csrf.server.ts
import { CSRF } from "remix-utils/csrf/server";
import { createCookie } from "react-router";
export const csrfCookie = createCookie("csrf", { httpOnly: true, secure: true });
export const csrf = new CSRF({
cookie: csrfCookie,
formDataKey: "csrfToken",
secret: "your-secret",
});
Apply CSRF protection in your root layout:
// routes/root.tsx
import { AuthenticityTokenProvider } from "remix-utils/csrf/react";
export async function loader({ request }) {
const [token, cookieHeader] = await csrf.commitToken();
return new Response(JSON.stringify({ token }), { headers: { "Set-Cookie": cookieHeader } });
}
export default function App({ loaderData }) {
return (
<AuthenticityTokenProvider token={loaderData.token}>
<Outlet />
</AuthenticityTokenProvider>
);
}
Securely process login actions and establish user sessions:
// routes/sign-in/action.ts
import { redirect } from "react-router";
import { csrf } from "~/lib/session/csrf.server";
import { serverAuth } from "~/lib/firebase/firebase.server";
export async function action({ request }) {
await csrf.validate(request);
const formData = await request.formData();
const idToken = formData.get("idToken")?.toString();
if (idToken) {
const sessionCookie = await serverAuth.createSessionCookie(idToken, { expiresIn: 604800000 }); // 7 days
const session = await getSession();
session.set("sessionCookie", sessionCookie);
return redirect("/", { headers: { "Set-Cookie": await commitSession(session) } });
}
return new Response("Invalid login.", { status: 400 });
}
Implement a session-refresh route to keep sessions valid:
// routes/api/action.refresh-session.ts
import { redirect } from "react-router";
import { serverAuth } from "~/lib/firebase/firebase.server";
export async function action({ request }) {
const session = await getSession(request.headers.get("Cookie"));
const idToken = (await request.formData()).get("idToken")?.toString();
if (!idToken) return redirect("/");
const sessionCookie = await serverAuth.createSessionCookie(idToken, { expiresIn: 604800000 });
session.set("sessionCookie", sessionCookie);
return new Response("Session refreshed.", { headers: { "Set-Cookie": await commitSession(session) } });
}
httpOnly
, secure
, and appropriate sameSite
settings.By following this guide, you've set up a robust and secure server-side authentication system, protecting your React Router v7 application while providing a smooth user experience.