nuxt-community / firebase-module

🔥 Easily integrate Firebase into your Nuxt project. 🔥
https://firebase.nuxtjs.org
MIT License
642 stars 98 forks source link

When refreshing the page I get the Error [FirebaseError]: Missing or insufficient permissions. #314

Closed GuasaPlay closed 3 years ago

GuasaPlay commented 4 years ago

Hi everyone. Every time the page is refreshed, it verifies if it is authenticated and also consults the data to cloud firestore. But the problem comes here, I get the following error. [FirebaseError]: Missing or insufficient permissions. I am using the authenticated ssr.

When I had the Cloud firestore rules so that any user can consult and read data, I did not get this error, but now that I established the rules so that only authenticated users have access, I started getting this error. Maybe any suggestions?

This is the code of nuxtServerInit:

async nuxtServerInit({ dispatch, commit }, { res, ctx }) {
    if (res && res.locals && res.locals.user) {
      const { allClaims: claims, idToken: token, ...authUser } = res.locals.user;

      await dispatch("onAuthStateChangedAction", {
        authUser,
        claims,
        token
      });
    }
  },

This is the onAuthStateChangedActioncode where I am consulting the data to cloud firestore:

async onAuthStateChangedAction({ commit, dispatch }, { authUser, claims }) {
    try {
      if (!authUser) {
        // await dispatch("cleanupAction");

        return;
      }

      const { uid, email, emailVerified, displayName, photoURL } = authUser;

      let respUsuario = await this.$fireStore.collection("usuarios").doc(uid).get();
      if (respUsuario.data().tipoUsuario == "FUNCIONARIO") {
        let respFuncionario = await this.$fireStore.collection("funcionarios").doc(uid).get();
        let user = {
          uid,
          emailVerified,
          displayName,
          photoURL,
          email,
          nombre1: respFuncionario.data().nombre1,
          apellido1: respFuncionario.data().apellido1,
          juntaCantonal: respFuncionario.data().juntaCantonal,
          tipoUsuario: respUsuario.data().tipoUsuario,
          rol: respFuncionario.data().rol
        };
        commit("SET_USER", user);
      }
    } catch (error) {
      console.log(error);
    }
  }

And these are the rules that are defined in my cloud firestore

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth.uid != null;
    }
  }
}
ChristophePetitjeanTalan commented 4 years ago

Exactly the same issue here !

Kazuto commented 4 years ago

I guess that the uid is not passed along with the request and therefor request.auth.uid is null.

Maybe try commit("SET_USER", user); before the request to firebase and append the remaining fields to the user.

ChristophePetitjeanTalan commented 4 years ago

Found the solution. In my case the worker wasn't running. By making it run, it works like a charm.

loicmarie commented 4 years ago

Thanks to @ChristophePetitjeanAi3 I just found out that my issue was caused by dev mode activated in workbox config. Make sure you have

pwa: {
  ...
  workbox: {
    dev: false,
    ...
  }
}

in your nuxt.config.js

loicmarie commented 4 years ago

I've been going a little fast, I don't know why but setting dev: false made res.locals empty in nuxtServerInit, so it worked because the call to firestore was made client side.

@lupas, in the doc it is stated "only set this true for testing", but in #134 you suggested to set dev: true. That is quite puzzling, so we can't simply put dev: process.NODE_ENV !== 'production' ? Should the value be the same for both dev and prod envs ? With false, I run into #134, and with true, I run into this one

bozobit commented 4 years ago

@GuasaPlay I am having EXACTLY the same issue and I'm quite a noob. Were you able to resolve it? My code is basically the same as yours.

@ChristophePetitjeanAi3 You mentioned that your worker wasn't working. What did you do fix it?

As far as the setting in PWA, I find that it works in dev mode on localhose with dev set to true. But when I deploy to firebase, I must set dev to false. But in both cases, a refresh causes the same error that @GuasaPlay described at the start of this thread. ... I've since done some more investigation into this, and it just appears that, for some reason, that the request.auth is null when fetching data from Firestore after a browser refresh. The user, however, appears still to be logged in via auth. @lupas, we would appreciate any help you could provide on this one.

GuasaPlay commented 4 years ago

@bozobit I still can't solve. Add to the credential and serverLogin configuration:

firebase: {
    config: { ... },
    services: {
      auth: {
        ssr: {
          credential: "~/assets/credential.json",
          serverLogin: true
        }
      },
      firestore: true,
      functions: true,
      storage: true
    }
  },

But it also doesn't work and now it shows me another error: FirebaseAppError: Credential implementation provided to initializeApp() via the "credential" property failed to fetch a valid Google OAuth2 access token with the following error: "Error fetching access token: Error while making request: getaddrinfo ENOTFOUND metadata.google.internal. Error code: ENOTFOUND".

If anyone has a solution about it please help

bozobit commented 4 years ago

@GuasaPlay, I think the problem is that, for some reason, the request.auth being sent to firebase after a refresh is null. I can't figure out why that is given that the user is authenticated. There is apparently some state when the get request is sent to firebase where the request.auth is null. I think if we figure that out, we will solve the problem. JUST MY GUESS!

bozobit commented 4 years ago

@GuasaPlay I have managed to duplicate the problem in the nuxt-fire-demo app. I created another firestore repository where I put in the following rules:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // messages collection
    match /message/{message} {
      allow read: if request.auth != null;
      allow write: if request.auth != null;
    }
  }
}

Then I created a test.vue page that is nuxt-linked directly from the home page. If I login and then use this link to navigate to the test page, the get from the database works properly. If I attempt to refresh the page, I get an 'insufficient priveleges' error, which I assume means that request.auth === null. If I then replace the rules above with the 'allow all' configuration

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if true;
    }
  }
}

I am again able to get the records. I'm quite new to all this, but I have tried lots of different ways to solve this, but have not been able to find an answer. I'm thinking it must be something quite basic!!!

I have posted the code here: https://github.com/bozobit/nuxt-fire-demo-fs-issue -- Perhaps others, including @lupas can have a look and advise.

lupas commented 4 years ago

Hey guys, sorry for the late response. Let me try to clear some things up:

In general: I think you guys don't all have the same issue, so I'll split my answers up:

First @bozobit:

As with your example stated here, you are not using serverLogin and therefore the Client SDK is also not meant to be logged in on server-side. This option only provides you with the information about the authenticated user, but we do not log in the Client SDK. Therefore, any calls you do in fetch() hooks are not authenticated.

I updated the documentation to make it more clear. If such calls shall be authenticated, one can use serverLogin - BUT this is experimental and might lead buggy - so I advice anyone against using it except for playing around.

@GuasaPlay

On contrary to bozobit you are using the experimental serverLogin, which tries to authenticate the Firebase Client SDK on the server by creating a new session for each user. This is not well-tested and runs into problems after a certain amount of traffic due to limitations by Firebase - so I don't advice you to use this for a productive website.

If you still want to play around with it:

Could you try setting loginDelay to 20 to test if it works then (see here)? Not sure if that could be it, but give it a try. As mentioned in the docs this is experimental so you might need to do some investigating yourself.

@Kazuto , @loicmarie, @ChristophePetitjeanAi3

Your problem is that the serviceWorker is/was not properly running and therefore the authUser came back empty. Seems most of you resolved the issue, if not please create a new issue for it so we can look at it separated from the other issue.

@loicmarie

Regarding the dev config: This shall always be false in production. For development, usually you can set it to true too. If you run into HRM issues thogh due to the PWA service worker, you can set it to false in dev (but in that case don't expect auth to work). So dev: process.NODE_ENV !== 'production' is fine. If you run into the GuasaPlay's issue now, follow what I wrote to GuasaPlay :)

In general

In my humble opinion SSR authentication is rarely necessary. I generally SSR all my non-authenticated content, and load auth-content dynamically (e.g. in the mounted() hook). I don't see much benefit in server-side render user-specific content that is not available to any of Google's crawlers anyway.

You can do this by e.g. by putting all user-authenticated data in tags and setting fetchOnServer to true, see here, or by loading your data in the mounted() hook.

And with the simple ssr: true option you at least know if a user is logged in or not and see the user claims, so based on that you can make most template decisions (e.g. show user dropdown in navbar, show "login" or "logout" and so on) so you can server render such elements easily.

Hope that helps at least a bit.

loicmarie commented 4 years ago

Thanks a lot @lupas this is a very clean explanation :+1:

One last word, when you say "SSR authentication is rarely necessary", how would you protect routes or make middleware redirections without server-side auth ? (while I am on the subject, it would be very useful to have more doc concerning route protection)

lupas commented 3 years ago

@loicmarie The way I wrote it is a bit wrong, what I meant is that server-side rendering user specific data is rarely necessary. With the simpe ssr: true option you can already check if the client is signed in, so that should suffice to make the call whether the route shall be protected or not. Then you just load & render the user specific content only on client side.

Regarding the docs: Will keep it in my head (see https://github.com/nuxt-community/firebase-module/issues/363) and probably create a good example at some point, thx!

dosstx commented 3 years ago

Excellent feedback @lupas !