Home > Blockchain >  Firebase 9, Remix: addDoc() returns "PERMISSION DENIED" error
Firebase 9, Remix: addDoc() returns "PERMISSION DENIED" error

Time:08-17

I have a remix app using firebase & a firestore database.

The sign in works fine, the GET /documents as well (no rules for GET)

When I try to create a new document with addDoc() I get a PERMISSION DENIED error.

My rules:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow create, update, delete: if request.auth != null && request.auth.token.email_verified && request.auth.token.email == '[email protected]';
      allow read: if true;
    }
  }
}

Since I use Remix (SSR) I am initializing firebase in the client-side:

if (getApps().length === 0) {
  initializeApp({
    apiKey: "...",
    authDomain: "...",
    projectId: "...",
    storageBucket: "...",
    messagingSenderId: "...",
    appId: "...",
    measurementId: "...",
  });
}

And I have firebase-admin in the server-side:

const serviceAccount = require("~/firebase-service-account.json");
serviceAccount.private_key_id = process.env.FIREBASE_PRIVATE_KEY_ID;
serviceAccount.private_key = process.env.FIREBASE_PRIVATE_KEY;

if (getApps().length === 0) {
  admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
  });
}

I use Google authentication in the client-side like this:

const submit = useSubmit();

const signInWithGoogle = async () => {
  const provider = new GoogleAuthProvider();
  const { user } = await signInWithPopup(auth, provider);

  if (user) {
    const formData = new FormData();
    formData.set("user", JSON.stringify(user));
    submit(formData, { method: "post" });
  }
};

Then the formData is handled by my server-side code and create a sessionCookie with auth().createSessionCookie().

Everything works fine in my dashboard (only accessible by logged-in users, I am using auth().verifySessionCookie() to check that) until I create a new document with the addDoc function from firebase 9. auth comes from firebase-admin package.

Here is the code I use:

const addDocument = async <T>(name: string, data: T): Promise<T> => {
  console.log(getAuth().currentUser) // null
  await addDoc(collection(db, name), data as Record<string, any>);

  cache().deleteEntry(name);

  return data;
};

When my write rule is set to true it works fine but when I put request.auth != null I have the permission error so I guess the problem is in the authentication part.

CodePudding user response:

While you may be using the Admin SDK on the server, that last code snippet is definitely a regular JavaScript SDK (as the Admin SDK doesn't have the concept of a currentUser). When you sign in the user on the client, that state does not automatically get transported to your server-side code.

The most common approach for accessing Firebase from the server until recently was to use the Admin SDK (which bypasses security rules) and perform any authorization checks in your application code.

You can use the regular JavaScript SDKs on the server, but you will have to ensure their sign-in state yourself. There is some work in progress to do this automatically for Next.js and Angular Universal, but not yet for Remix.run. How these work (and what you'd have to do) is that they:

  1. Pass the sign-in information in session cookies
  2. Verify the ID token in the server-side code with Admin SDK.
  3. If the verification succeeds, they mint a custom token for the user.
  4. And then sign in with that custom token in the regular JavaScript SDK.

You'll need to ensure that you have a separate FirebaseApp/Auth instance for each user, and will probably want to set up a LRU cache for those to prevent having to perform that translation on each call.

  • Related