Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 65 additions & 14 deletions examples/firebase-auth-firestore/app/routes/login.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
import type { ActionFunction, LoaderFunction } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { Link, useActionData } from "@remix-run/react";
import {
Link,
useActionData,
useLoaderData,
useSubmit,
} from "@remix-run/react";
import { useCallback, useState } from "react";

import { checkSessionCookie, signIn } from "~/server/auth.server";
import * as firebaseRest from "~/firebase-rest";
import {
checkSessionCookie,
signIn,
signInWithToken,
} from "~/server/auth.server";
import { commitSession, getSession } from "~/sessions";
import { getRestConfig } from "~/server/firebase.server";

interface LoaderData {
apiKey: string;
domain: string;
}
export const loader: LoaderFunction = async ({ request }) => {
const session = await getSession(request.headers.get("cookie"));
const { uid } = await checkSessionCookie(session);
Expand All @@ -14,22 +30,31 @@ export const loader: LoaderFunction = async ({ request }) => {
if (uid) {
return redirect("/", { headers });
}
return json(null, { headers });
const { apiKey, domain } = getRestConfig();
return json<LoaderData>({ apiKey, domain }, { headers });
};

type ActionData = {
interface ActionData {
error?: string;
};

}
export const action: ActionFunction = async ({ request }) => {
const form = await request.formData();
const email = form.get("email");
const password = form.get("password");
const formError = json({ error: "Please fill all fields!" }, { status: 400 });
if (typeof email !== "string") return formError;
if (typeof password !== "string") return formError;
const idToken = form.get("idToken");
let sessionCookie;
try {
const sessionCookie = await signIn(email, password);
if (typeof idToken === "string") {
sessionCookie = await signInWithToken(idToken);
} else {
const email = form.get("email");
const password = form.get("password");
const formError = json(
{ error: "Please fill all fields!" },
{ status: 400 }
);
if (typeof email !== "string") return formError;
if (typeof password !== "string") return formError;
sessionCookie = await signIn(email, password);
}
const session = await getSession(request.headers.get("cookie"));
session.set("session", sessionCookie);
return redirect("/", {
Expand All @@ -44,12 +69,38 @@ export const action: ActionFunction = async ({ request }) => {
};

export default function Login() {
const [clientAction, setClientAction] = useState<ActionData>();
const action = useActionData<ActionData>();
const restConfig = useLoaderData<LoaderData>();
const submit = useSubmit();

const handleSubmit = useCallback(
async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line is my main issue with this approach, as if something fails after this point it will not fall back to the form post + server side auth

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I'm fine with this. The same issue would exist when using a Remix <Form /> which does event.preventDefault() as well.

// To avoid rate limiting, we sign in client side if we can.
const login = await firebaseRest.signInWithPassword(
{
email: event.currentTarget.email.value,
password: event.currentTarget.password.value,
returnSecureToken: true,
},
restConfig
);
if (firebaseRest.isError(login)) {
setClientAction({ error: login.error.message });
return;
}
submit({ idToken: login.idToken }, { method: "post" });
},
[submit, restConfig]
);
return (
<div>
<h1>Login</h1>
{action?.error && <p>{action.error}</p>}
<form method="post">
{(clientAction?.error || action?.error) && (
<p>{clientAction?.error || action?.error}</p>
)}
<form method="post" onSubmit={handleSubmit}>
<input
style={{ display: "block" }}
name="email"
Expand Down
4 changes: 4 additions & 0 deletions examples/firebase-auth-firestore/app/server/auth.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ export const requireAuth = async (request: Request): Promise<UserRecord> => {

export const signIn = async (email: string, password: string) => {
const { idToken } = await auth.signInWithPassword(email, password);
return signInWithToken(idToken);
};

export const signInWithToken = async (idToken: string) => {
const expiresIn = 1000 * 60 * 60 * 24 * 7; // 1 week
const sessionCookie = await auth.server.createSessionCookie(idToken, {
expiresIn,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import {
} from "firebase-admin/app";
import { getAuth as getServerAuth } from "firebase-admin/auth";

import * as firebaseRest from "./firebase-rest.server";
import * as firebaseRest from "../firebase-rest";

const getRestConfig = (): {
// Warning: though getRestConfig is only run server side, its return value may be sent to the client
export const getRestConfig = (): {
apiKey: string;
domain: string;
} => {
Expand Down