firebase / firebase-admin-node

Firebase Admin Node.js SDK
https://firebase.google.com/docs/admin/setup
Apache License 2.0
1.59k stars 358 forks source link

Various issues when used with Cloudflare Pages #2069

Open JorritSalverda opened 1 year ago

JorritSalverda commented 1 year ago

[READ] Step 1: Are you in the right place?

When running the following Typescript code in Cloudflare Pages on the server-side - in a Remix server.ts file:

import { applicationDefault, initializeApp as initializeAdminApp } from "firebase-admin/app";

initializeAdminApp({
  credential: applicationDefault(),
})

I get the following error:

[pages:err] TypeError: globalThis.XMLHttpRequest is not a constructor
    at checkTypeSupport (/private/Users/jorrit/work/simpl/simpl-energy-website/functions/[[path]].js:15751:17)
    at node_modules/rollup-plugin-node-polyfills/polyfills/http-lib/capability.js (/private/Users/jorrit/work/simpl/simpl-energy-website/functions/[[path]].js:15764:177)
    at /private/Users/jorrit/work/simpl/simpl-energy-website/functions/[[path]].js:7:52
    at node_modules/rollup-plugin-node-polyfills/polyfills/http-lib/request.js (/private/Users/jorrit/work/simpl/simpl-energy-website/functions/[[path]].js:17874:5)
    at /private/Users/jorrit/work/simpl/simpl-energy-website/functions/[[path]].js:7:52
    at node-modules-polyfills:http (/private/Users/jorrit/work/simpl/simpl-energy-website/functions/[[path]].js:18033:5)
    at /private/Users/jorrit/work/simpl/simpl-energy-website/functions/[[path]].js:7:52
    at node-modules-polyfills-commonjs:http (/private/Users/jorrit/work/simpl/simpl-energy-website/functions/[[path]].js:18133:21)
    at /private/Users/jorrit/work/simpl/simpl-energy-website/functions/[[path]].js:10:47
    at node_modules/firebase-admin/lib/utils/api-request.js (/private/Users/jorrit/work/simpl/simpl-energy-website/functions/[[path]].js:22423:76)

This is caused by Cloudflare Workers only supporting fetch not XMLHttpRequest.

Another issue I run into when using

import * as admin from "firebase-admin";
import { applicationDefault, initializeApp as initializeAdminApp } from "firebase-admin/app";

initializeAdminApp({
  credential: applicationDefault(),
})

const adminAuth = admin.auth();

Is that I get the following error:

✘ [ERROR] Could not resolve "@firebase/database-compat/standalone"

    node_modules/firebase-admin/lib/app/firebase-namespace.js:106:41:
      106 │ ...ject.assign(fn, require('@firebase/database-compat/standalone'));
          ╵                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  The path "./standalone" is not currently exported by package "@firebase/database-compat":

    node_modules/@firebase/database-compat/package.json:16:13:
      16 │   "exports": {
         ╵              ^

  None of the conditions provided ("types", "node") match any of the currently active conditions ("default", "require", "worker"):

    node_modules/@firebase/database-compat/package.json:31:20:
      31 │     "./standalone": {
         ╵                     ^

  Consider enabling the "types" condition if this package expects it to be enabled. You can use "conditions: ['types']" to do that:

    node_modules/@firebase/database-compat/package.json:32:6:
      32 │       "types": "./dist/database-compat/src/index.standalone.d.ts",
         ╵       ~~~~~~~

  You can mark the path "@firebase/database-compat/standalone" as external to exclude it from the bundle, which will remove this error. You can also surround this "require" call with a try/catch block to handle this failure at run-time instead of bundle-time.

Any chance this npm package can be made compatible with Cloudflare Pages/Workers?

[REQUIRED] Step 2: Describe your environment

[REQUIRED] Step 3: Describe the problem

Steps to reproduce:

npx create-remix@latest

? Where would you like to create your app? ./my-remix-app
? What type of app do you want to create? Just the basics
? Where do you want to deploy? Choose Remix App Server if you're unsure; it's easy to change deployment targets. Cloudflare Pages
? TypeScript or JavaScript? TypeScript
? Do you want me to run `npm install`? Yes

cd my-remix-app
npm install firebase
npm install firebase-admin
touch ./app/firebase.server.ts

Add the following content to app/firebase.server.ts:

import * as admin from "firebase-admin";
import { applicationDefault, initializeApp as initializeAdminApp } from "firebase-admin/app";
import { signInWithEmailAndPassword, getAuth } from "firebase/auth";

// errors with 'TypeError: globalThis.XMLHttpRequest is not a constructor'
initializeAdminApp({
  credential: applicationDefault(),
})

// errors with 'Could not resolve "@firebase/database-compat/standalone"'
const adminAuth = admin.auth();

async function signIn(email: string, password: string) {
  const auth = getAuth();
  return signInWithEmailAndPassword(auth, email, password);
}

export { signIn }

And update the app/routes/index.tsx file to

import type { ActionArgs } from "@remix-run/cloudflare";
import { Form } from "@remix-run/react";
import { signIn } from "~/firebase.server";

export const action = async ({ request }: ActionArgs) => {
  let formData = await request.formData();

  let email = formData.get("email");
  let password = formData.get("password");

  const { user } = await signIn(email as string, password as string);
}

export default function Index() {
  return (
    <Form method="post">
      <label>
        Email
        <input type="email" name="email" />
      </label>
      <label>
        Password
        <input type="password" name="password" />
      </label>
      <button type="submit">
        Sign In
      </button>
    </Form>
  );
}

Now run npm run dev and see it fail.

lahirumaramba commented 1 year ago

Hi @JorritSalverda ,

firebase-admin requires a full Node.js runtime and is not supported on Cloudflare workers. The SDK is currently incompatible with bundling tools like webpack.

JorritSalverda commented 1 year ago

Hi @lahirumaramba

That's a pity. I think it has mostly to do with the database access. If I store session state in Cloudflare's KV store instead and check the JWT with a library that does run in Cloudflare Workers I don't need firebase-admin and am just fine. It does block me from using Firestore though.

koistya commented 1 year ago

As a workaround, it is possible to interact with Firestore (and other Google APIs) from under Cloudflare Workers or Cloudflare Pages using Google's REST API, for example:

import { Hono } from "hono"; // routing library for Cloudfalre Workers
import { getAccessToken } from "web-auth-library/google"; // auth library for Cloudflare Workers

const app = new Hono();

app.get("/example", ({ env, executionCtx }) => {
  const accessToken = await getAccessToken({
    credentials: env.GOOGLE_CLOUD_CREDENTIALS, // GCP service account key (JSON)
    scope: "https://www.googleapis.com/auth/cloud-platform",
    waitUntil: executionCtx.waitUntil, // allows the token to be refreshed in the background
  });
  const prefixURL = `https://firestore.googleapis.com/v1/projects/${env.GOOGLE_CLOUD_PROJECT}/databases/(default)/documents`;
  const res = await fetch(`${prefixURL}/cities/LA`, {
    headers: { "Authorization": `Bearer ${accessToken}` }
  });
  ...
});

https://github.com/kriasoft/web-auth-library

lahirumaramba commented 1 year ago

Hey folks, I am gathering details on similar use cases (this is not a promise for a feature, btw :)). Right now, unfortunately the SDK does not work on edge runtimes, and as @koistya mentioned using the REST API would be your best bet for now. If you have some time, could you folks share a few more details about your use-cases, please? This will help us better understand your requirements and consider supporting edge and other similar runtimes in the future.

For example, are your use cases mainly around validating tokens? or do you need access to other Firebase services and why etc. Thank you!

chrisspiegl commented 1 year ago

I just found this question while researching to use Cloudflare workers for SSR in one of my projects. A connection to firestore would be crucial for us in this regard.

Our tech stack is based on NUXT.js 3 and VueFire.

Having edge functions render SSR content would help with performance a lot. And not having to individually wrap the REST api would make things incredibly easy.

Cheers.

chrisspiegl commented 1 year ago

Adding to this, it seems like Cloudflare is working on Node.js compatibility for things like this. (Source Blog Post)

But, I have been trying to get Cloudflare and Firebase / Firestore SSR to work with polyfills like Unenv and all that (this was before the new blog post from Cloudflare).

Feels like I came pretty far but then Firebase decides to use another freaking node.js library and it all breaks again.

lahirumaramba commented 1 year ago

Thanks for sharing this @chrisspiegl ! I have been running some experiments on validating Firebase tokens on edge runtimes here lahirumaramba/edge_token_verifier, but it sounds like the ask is for more than just validating JWTs (access to RTDB/Firestore services etc.).

chrisspiegl commented 1 year ago

Hi @lahirumaramba,

App Check is also something interesting but for me it would be more interesting or rather necessary for my hobby/prototype to get Firestore and Auth working 🙈.

I already got SSR with Firebase Auth & Firestore working on Vercel (not sure if what i got running are edge functions or just functions?). (I shared an example repo based on Nuxt.js 3 here).

But Cloudflare is a completely different beast since it is a much more limited edge engine.

Now, I got extremely excited when I read one of the announcement tweets about them adding node.js… but that was quickly shredded since they are just slowly rolling out certain compatibilities.

In my testing with the unenv aliases, I noticed that whatever I am trying, there is always another library being used which is only "auto mocked" by unenv which mens it's not really there just pretending to be there and hoping it's not actually being used 🙈.

This kinda works with things like os.platform() but not so much with the http2 library usage and stuff like that. (if os.platform() is actually just used like that… inside firebase-admin it's actually tested against with os.platform().length > 3 which is not something the auto mock polyfill works with and it throws since it can't Cannot convert object to primitive value). Oh so many roadblocks I have run into…

I also wonder how for example the zip library would be polyfilled… if that would even be possible.

On the other hand… it feels like there could be some work done to get the node firebase admin sdk compatible with Cloudflare and these types of edge workers. I'd be fine with that being done with polyfill usage. Even just that 👍.

Cheers

mgarf commented 1 year ago

also want to +1 on getting Firebase Auth for edge runtimes.

emalamisura-c2m commented 1 year ago

+1 for this, really annoying and quickly becoming the norm with SSR on edge.

maccman commented 1 year ago

+1

brian-gee commented 8 months ago

+1, can't believe such a standard feature isn't supported.

step135 commented 8 months ago

It could be done directly and easy through REST API calls with admin privileges. Managing user accounts would be the base. But nothing is described in documentation and it looks like Firebase is already dying as they don't work on including this and many other trivial functionalities that other databases already have.

gustavopch commented 8 months ago

I’ve been saying the same thing. Compare Firebase releases with Supabase. Firebase looks mostly stopped in time. I’ve already lost much of my hope in its future.

step135 commented 6 months ago

And for them it is a question of a short moment to describe REST API for admins to at least manage user accounts. But they don't do it. Does anyone know why?

ajzbc commented 6 months ago

+1

clibequilibrium commented 5 months ago

And for them it is a question of a short moment to describe REST API for admins to at least manage user accounts. But they don't do it. Does anyone know why?

Do you mean this? https://firebase.google.com/docs/reference/rest/auth

treeder commented 5 months ago

Would anyone be interested in a lightweight library using only the REST APIs for these Google services? We've written our own little JS modules to access these services from Cloudflare and Bun apps (neither of which work with Google's libraries).

clibequilibrium commented 5 months ago

Found this specifically for admin sdk support on Cloudflare network https://github.com/Marplex/flarebase-auth

step135 commented 5 months ago

Found this specifically for admin sdk support on Cloudflare network https://github.com/Marplex/flarebase-auth

Useless as it can not manage accounts (list and delete).

clibequilibrium commented 5 months ago

Found this specifically for admin sdk support on Cloudflare network https://github.com/Marplex/flarebase-auth

Useless as it can not manage accounts (list and delete).

Fork it and add delete.

https://firebase.google.com/docs/reference/rest/auth#section-delete-account

EDIT: delete exists

https://github.com/Marplex/flarebase-auth/blob/main/src/lib/flarebase-auth.ts#L142

    /**
     * Delete a current user
     * @param idToken   A Firebase Auth ID token for the user.
     */
    deleteAccount(idToken: string): Promise<void>;
clibequilibrium commented 5 months ago

Another solution could be enabling nodejs_compat https://developers.cloudflare.com/workers/runtime-apis/nodejs/ to enable compatibility with nodejs. But you would need to fork admin sdk and do as per documentation:

// Do this:
import { Buffer } from 'node:buffer';

// Do not do this:
import { Buffer } from 'buffer';
prvashisht commented 4 months ago

+1, Firestore via admin SDK is breaking in Cloudflare pages in my Nuxt3 application's server routes.

gustavopch commented 4 months ago

Some functions I wrote with help of GPT-4 to use Firestore via its REST API: https://gist.github.com/gustavopch/ca4d15faa5ab2e76ba918b7adeba7fa2

My advise: don't count on Firebase shipping anything.