Skip to content

Conversation

@ccssmnn
Copy link
Contributor

@ccssmnn ccssmnn commented Feb 5, 2022

This PR adds a Firebase example with Auth-flow and minimal to-do functionality based on Firestore.

  • app/server/firebase.server.ts initializes the client & server SDKs
  • app/server/auth.server.ts implements auth utilities to sign-in users or to protect routes and actions
  • app/server/db.server.ts shows how to utilize FirestoreDataConverters to have TypeScript support when dealing with Firestore

@remix-cla-bot
Copy link
Contributor

remix-cla-bot bot commented Feb 5, 2022

Hi @ccssmnn,

Welcome, and thank you for contributing to Remix!

Before we consider your pull request, we ask that you sign our Contributor License Agreement (CLA). We require this only once.

You may review the CLA and sign it by adding your name to contributors.yml.

Once the CLA is signed, the CLA Signed label will be added to the pull request.

If you have already signed the CLA and received this response in error, or if you have any questions, please contact us at [email protected].

Thanks!

- The Remix team

@remix-cla-bot
Copy link
Contributor

remix-cla-bot bot commented Feb 5, 2022

Thank you for signing the Contributor License Agreement. Let's get this merged! 🥳

@dpcesx
Copy link

dpcesx commented Feb 11, 2022

This is awesome! Thanks, @ccssmnn I saw your answer to a firebase auth question, but this is exactly what I needed.

#900

@Ehesp
Copy link

Ehesp commented Feb 14, 2022

@davideast Might be a good one for you to review :)

@pedrogius
Copy link

Should the client sdk be used on the server? Saw this discussion on twitter where they say it should be called on the client and pass the idToken on the action.

@Ehesp
Copy link

Ehesp commented Feb 18, 2022

I already mentioned that in another comment here. I personally don't think it's the best approach to use the client sdk on the server since state is stored within the sdk so I don't know if it'll cause any adverse side effects.

@kentcdodds
Copy link
Member

Just so long as you're not unnecessarily using the sdk on the client. I haven't had a chance to review this yet, but I've successfully gotten firebase working with Remix with no client-side code required and that's what I'd want for the example if possible.

@dpcesx
Copy link

dpcesx commented Feb 21, 2022

@kentcdodds I'm still playing with my implementation of firebase/remix. I've seen one of your youtube videos on firebase, but if you have others I can try and test some other approaches as well.

@ccssmnn
Copy link
Contributor Author

ccssmnn commented Feb 21, 2022

There is more to Firebase where things might need to be done differently when doing them the Remix way:

  • Storage
    • Uploads (MultipartFormData)
    • Downloads (e.g. Images, pass them through Remix vs. storing them under a secure but public path)
  • Database listeners / real-time updates (WebSocket API #389 & Refresh Route Data API #1996)
  • sign-in with Google, Apple, ... (sprinkle in Firebase Client SDK?)
  • Cloud Functions / reacting to database events (handle them entirely in Cloud Functions or use Cloud Functions to trigger APIs implemented in Remix?)
  • Hosting (Firebase Hosting + Cloud Run seems to be the best way right now)

I think creating one example for each of these tasks will bloat the example directory. One well-structured example for all things Remix+Firebase would be wonderful. We could enhance this example, once nice approaches for these features are found.

@dpcesx
Copy link

dpcesx commented Feb 21, 2022

There is more to Firebase where things might need to be done differently when doing them the Remix way:

  • Storage

    • Uploads (MultipartFormData)
    • Downloads (e.g. Images, pass them through Remix vs. storing them under a secure but public path)
  • Database listeners / real-time updates (WebSocket API #389 & Refresh Route Data API #1996)

  • sign-in with Google, Apple, ... (sprinkle in Firebase Client SDK?)

  • Cloud Functions / reacting to database events (handle them entirely in Cloud Functions or use Cloud Functions to trigger APIs implemented in Remix?)

  • Hosting (Firebase Hosting + Cloud Run seems to be the best way right now)

I think creating one example for each of these tasks will bloat the example directory. One well-structured example for all things Remix+Firebase would be wonderful. We could enhance this example, once nice approaches for these features are found.

I need to work on a few of those items over the next month.

  • Storage
    • Uploads (MultipartFormData)
    • Downloads (e.g. Images, pass them through Remix vs. storing them under a secure but public path)
  • Cloud Functions (need more info on what you have in mind for this example repo)

If you have a way you would like to coordinate the effort please let me know. Otherwise, I will keep checking here and update you with any progress.

@kentcdodds
Copy link
Member

I'm fine having one sizeable example that's the one-stop example for all things firebase 👍

Comment on lines +30 to +34
const { user } = await signInWithEmailAndPassword(
auth.client,
email,
password
);
Copy link

Choose a reason for hiding this comment

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

I don't think it's safe to use the firebase client sdk on server-side. It is a stateful lib that has the concept of a "current user".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From the Firebase Docs on the client SDK:

Follow this guide to use the Firebase JavaScript SDK in your web app or as a client for end-user access, for example, in a Node.js desktop or IoT application.

Of course, you can mess up your server implementation. I don't yet see why receiving the user directly from the sign-in or verify-token calls should be unsafe. See this comment.

Copy link

Choose a reason for hiding this comment

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

FWIW I've been dealing with this recently myself. I'm not sure if what I did is correct, but I decided to handle the authentication purely on the client, then I POST the token to the server where it is verified and then set the session cookie. I previously did the auth on the server like this example, but then I could not use the federated logins (like Apple, Facebook, Google, etc). Handling it on the client side was the only way I could get the federated logins to work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's totally fine to use Firebase on the client and send the JWT through a cookie for the following requests. This example is to show how to use Firebase in your Remix app without using Firebase on the client. From there on you can enhance with client side js

Copy link
Contributor

@penx penx May 27, 2022

Choose a reason for hiding this comment

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

Follow this guide to use the Firebase JavaScript SDK in your web app or as a client for end-user access, for example, in a Node.js desktop or IoT application.

for end-user access

i.e. not for a server handling requests for multiple users

in a Node.js desktop

i.e. where there would only be a single user at a time

IoT application

Similarly, I think they are inferring where you would have a single user at a time.

As @jadlr mentioned, the Firebase client SDK is stateful. When you sign in, this user remains signed in for all requests until the next sign in attempt. If you were to use firebase/firestore server side, I'm pretty sure it would act as whoever the last person to sign in was.

Sure, it is likely possible to avoid revealing security holes to end users by limiting access to the API (e.g. by only using the sign in API, not using firebase/firestore, and not revealing tokens/server state to users), but I don't think it's a good idea to put this out there as a recommended approach without confirmation from the Firebase team that the client SDK is built and tested for this purpose.

See this comment.

This is a comment saying that they use it in integration tests. I'm not convinced this means it has been built and tested for the purpose of a multi user enviroment.

Copy link
Contributor

@penx penx May 27, 2022

Choose a reason for hiding this comment

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

I'm in the process of integrating Firebase auth in to a Remix app via the Firebase Auth and Firestore REST APIs. I'll try report back on how I get on. I have this working via the Firebase emulator but not in production. I saw issues with rate limiting being mentioned on Twitter which I think would apply to both the REST and client SDK approaches.

Copy link
Contributor Author

@ccssmnn ccssmnn May 30, 2022

Choose a reason for hiding this comment

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

@penx thank you for investigating this further. Indeed, the firebase sdk's don't seem to be intended to be used this way, which may or may not cause risks using this in production. I didn't have the time to investigate the REST APIs. I'm really looking forward to seeing the results of your investigations!

Copy link
Contributor

@penx penx May 30, 2022

Choose a reason for hiding this comment

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

I have auth working with the Firebase REST API now. I plan to work on some Firebase related PRs for Remix in the coming weeks, including updating the example to use the REST API.

I think I've also figured a way to get around the rate limiting when used server side so will include that if it works. As far as I'm aware the rate limiting effects both the REST API and the client side SDK when used from the same IP.

My plan is to authenticate on the client via the REST API if the JavaScript has loaded, altering the body sent to the action in the process, and fall back to doing it in the action JavaScript has not loaded or failed. Then when the action received a request, it would either:

  • contain username/password => fallback to server side login via rest API
  • contain response from Firebase after a client side login => server side validate the session before saving in to Remix session (as validation would not be subject to rate limiting)

Copy link
Contributor

@penx penx Jun 1, 2022

Choose a reason for hiding this comment

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

@kentcdodds kentcdodds added the needs-response We need a response from the original author about this issue/PR label Mar 7, 2022
@MichaelDeBoey MichaelDeBoey changed the title Add Example for Firebase Auth & Firestore docs(examples): add Firebase Auth + Firestore example Mar 18, 2022
@ccssmnn
Copy link
Contributor Author

ccssmnn commented Mar 31, 2022

This PR has the needs-response label. What is expected is not clear to me, though. I think, that a full Firebase example needs more Firebase features covered as described here This can still be a useful example for people interested in using Firebase and Remix together.

@github-actions github-actions bot removed the needs-response We need a response from the original author about this issue/PR label Mar 31, 2022
@kentcdodds
Copy link
Member

Sounds good to me 👍 Thanks for following up!

@mocon
Copy link

mocon commented Apr 6, 2022

This was SUPER helpful. Thanks everyone! One small change I had to make was to destructure the imports in firebase.server.ts:

import {
  getApps as getServerApps,
  initializeApp as initializeServerApp,
  cert as serverCert,
} from 'firebase-admin/app'
import { getApps as getClientApps, initializeApp as initializeClientApp } from 'firebase/app'
import { getAuth as getServerAuth } from 'firebase-admin/auth'
import { getAuth as getClientAuth } from 'firebase/auth'

if (getClientApps().length === 0) {
  initializeClientApp(JSON.parse(process.env.CLIENT_CONFIG as string))
}

if (getServerApps().length === 0) {
  initializeServerApp({
    credential: serverCert(JSON.parse(process.env.SERVICE_ACCOUNT as string)),
  })
}

export const auth = {
  server: getServerAuth(),
  client: getClientAuth(),
}

@johnpolacek
Copy link
Contributor

@mocon I was not able to get the Remix Firebase Auth example to work until I applied your update to firebase.server.ts. You should PR that sir :)

penx added a commit to penx/remix that referenced this pull request Jun 1, 2022
Apply fixes as per comment by @mocon at remix-run#1811 (comment)

Also:

- throw meaningul errors when environment variables are missing or can't be parsed.
- check for existence of environment variable rather than cast to string
penx added a commit to penx/remix that referenced this pull request Jun 1, 2022
Apply fixes as per comment by @mocon at remix-run#1811 (comment)

Also:

- throw meaningul errors when environment variables are missing or can't be parsed.
- check for existence of environment variable rather than cast to string
penx added a commit to penx/remix that referenced this pull request Jun 1, 2022
Apply fixes as per comment by @mocon at remix-run#1811 (comment)

Also:

- throw meaningul errors when environment variables are missing or can't be parsed.
- check for existence of environment variable rather than cast to string
penx added a commit to penx/remix that referenced this pull request Jun 1, 2022
Use the Firebase REST API for authenticating server side.

The client SDK is stateful. It _may_ not pose a security risk if it is _only_ used for authentication calls, but could lead to severe security issues if users were to also address other libraries such as Firestore through the client SDK.

The Firebase Auth REST API gives us equivalent functionality.

As far as I'm aware, there are rate limiting issues with both approaches, which I plan to address in a follow up PR.

This was previously discussed at remix-run#1811 (comment)
penx added a commit to penx/remix that referenced this pull request Jun 1, 2022
Use the Firebase REST API for authenticating server side.

The client SDK is stateful. It _may_ not pose a security risk if it is _only_ used for authentication calls, but could lead to severe security issues if users were to also address other libraries such as Firestore through the client SDK.

The Firebase Auth REST API gives us equivalent functionality.

As far as I'm aware, there are rate limiting issues with both approaches, which I plan to address in a follow up PR.

This was previously discussed at remix-run#1811 (comment)
MichaelDeBoey pushed a commit to penx/remix that referenced this pull request Jun 2, 2022
Use the Firebase REST API for authenticating server side.

The client SDK is stateful. It _may_ not pose a security risk if it is _only_ used for authentication calls, but could lead to severe security issues if users were to also address other libraries such as Firestore through the client SDK.

The Firebase Auth REST API gives us equivalent functionality.

As far as I'm aware, there are rate limiting issues with both approaches, which I plan to address in a follow up PR.

This was previously discussed at remix-run#1811 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.