nuxt-community / firebase-module

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

User object null on hard refresh or direct navigation from browser window even after using nuxtServerInit action #452

Closed dosstx closed 3 years ago

dosstx commented 3 years ago

I have a similar problem like this one: https://github.com/nuxt-community/firebase-module/issues/252

However, I tried the proposed solution but I am unsure if I did it correctly, thus posting here for help:

Here's my problem: When using middleware, the user store object is coming back as null if I try to access a page directly such as typing in the URL vs navigating to it.

There is no problem when navigating, but the issue is in directly accessing a page by typing in the URL in the browser window or doing a hard refresh. My use case is trying to prevent a signed in user from accessing the login page (or sign up page), thus I thought I'd write a middleware like so but console logging it shows user is null on direct page access and hard refresh.

middleware/auth

export default function ({ app, store, redirect }) {
  console.log('user?', store.state.user.currentUser)
}

I've followed the Auth SSR tutorial, but perhaps I am making a mistake somewhere. Can someone help me get this configured properly?

nuxt.config.js:

export default {
  vue: {
    config: {
      productionTip: false,
      devtools: true
    }
  },
  publicRuntimeConfig: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000'
  },
  privateRuntimeConfig: {},
  head: {
    htmlAttrs: {
      lang: 'en'
    },
    title: 'pixstery',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: '' }
    ],
    link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
  },

  // Global CSS (https://go.nuxtjs.dev/config-css)
  css: [],

  // Plugins to run before rendering page (https://go.nuxtjs.dev/config-plugins)
  plugins: [
    // {
    //   src: '~/plugins/vuex-persist',
    //   mode: 'client'
    // }
  ],

  // Auto import components (https://go.nuxtjs.dev/config-components)
  components: true,

  // Modules for dev and build (recommended) (https://go.nuxtjs.dev/config-modules)
  buildModules: [
    // https://go.nuxtjs.dev/eslint
    '@nuxtjs/eslint-module',
    // https://go.nuxtjs.dev/tailwindcss
    '@nuxtjs/tailwindcss',
    '@nuxtjs/html-validator'
  ],

  // Modules (https://go.nuxtjs.dev/config-modules)
  modules: ['@nuxtjs/pwa', '@nuxtjs/firebase', '@nuxt/content'],
  firebase: {
    config: {
      apiKey: 'AIzaSyD4ge8g6oe0xp6vQ1y85AbvI8PXcDKzoKk',
      authDomain: 'pixstery.firebaseapp.com',
      databaseURL: '<databaseURL>',
      projectId: 'pixstery',
      storageBucket: 'pixstery.appspot.com',
      messagingSenderId: '471446831599',
      appId: '1:471446831599:web:16ec77402cbb336f67b61a',
      measurementId: 'G-XFVH7QFG4L' // optional
    },
    services: {
      auth: {
        persistence: 'local', // default
        initialize: {
          onAuthStateChangedAction: 'user/onAuthStateChangedAction',
          subscribeManually: false
        },
        ssr: true
        // emulatorPort: 9099,
        // emulatorHost: 'http://localhost'
      },
      firestore: {
        chunkName:
          process.env.NODE_ENV !== 'production' ? 'firebase-auth' : '[id]', // default
        enablePersistence: true
        // emulatorPort: 8080,
        // emulatorHost: 'localhost'
      },
      functions: {
        location: 'us-central1'
        // emulatorPort: 12345,
        // emulatorHost: 'http://10.10.10.3'
      },
      storage: true
    }
  },
  pwa: {
    // disable the modules you don't need
    meta: false,
    icon: false,
    // if you omit a module key form configuration sensible defaults will be applied
    // manifest: false,

    workbox: {
      importScripts: [
        // ...
        '/firebase-auth-sw.js'
      ],
      // by default the workbox module will not install the service worker in dev environment to avoid conflicts with HMR
      // only set this true for testing and remember to always clear your browser cache in development
      // dev: process.env.NODE_ENV === 'development'
      dev: true
    }
  },

  // Content module configuration (https://go.nuxtjs.dev/config-content)
  content: {},

  // Build Configuration (https://go.nuxtjs.dev/config-build)
  build: {},
  router: {
    middleware: ['auth']
  }
}

store/user:

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

      await dispatch('onAuthStateChangedAction', {
        authUser,
        claims,
        token
      })
    }
  },
  async onAuthStateChangedAction({ commit, dispatch }, { authUser, claims }) {
    console.log('auth state changed....')
    console.log('authUser: ', authUser)
    console.log('claims: ', claims)

    try {
      if (!authUser) {
        await dispatch('logOutUser')
        return
      } else if (authUser && authUser.emailVerified) {
        const {
          uid,
          email,
          emailVerified,
          displayName = '',
          photoURL,
          metadata,
          providerData,
          providerId,
          tenantId
        } = authUser
        commit('SET_AUTH_USER', {
          uid,
          email,
          emailVerified,
          displayName,
          photoURL, // results in photoURL being undefined for server auth
          metadata,
          providerData,
          providerId,
          tenantId,
          isAdmin: claims.custom_claim // use custom claims to control access (see https://firebase.google.com/docs/auth/admin/custom-claims)
        })
        console.log('fetching profile...')
        await dispatch('getUserProfile', authUser)
      } else {
        console.log('User logged out or not verified')
        return
      }
    } catch (error) {
      console.error('Error with Auth State observer: ', error)
    }
  },

So, with above code, hard refresh (or direct navigation via browser URL) of my login page (when user is already logged in) shows this in console (note the console log of user?, null from middleware)

image

Thank you for any help!

yuyusalim-ms commented 3 years ago

I observe the same behavior where res.locals is {} on hard refresh.

dosstx commented 3 years ago

I haven't tested it yet, but possible issue could be the following (mentioned in Nuxtjs docs):

Only the primary module (in store/index.js) will receive the NuxtServerInit action. You'll need to chain your module actions from there.

If that's the case, my nuxtServerInit is being called from store/user.js ... maybe that's why I have the problem. @yuyusalim-ms can you confirm yours?

dosstx commented 3 years ago

So after moving user logic to store/index.js and putting nuxtServerInit action there...

export const state = () => ({
  currentUser: null,
  userProfile: null
})

export const mutations = {
  SET_AUTH_USER(state, payload) {
    state.currentUser = payload
  },
  SET_USER_PROFILE(state, payload) {
    state.userProfile = payload
  }
}

export const actions = {
  // Store action called nuxtServerInit:
  async nuxtServerInit({ dispatch, commit }, { res }) {
    if (res && res.locals && res.locals.user) {
      console.log('nuxt server init: ', res.locals)
      const { allClaims: claims, idToken: token, ...authUser } = res.locals.user

      await dispatch('onAuthStateChangedAction', {
        authUser,
        claims
        // token
      })
    }
  },

...did not fix the problem for me. Still get null user object on hard refresh of page.

yuyusalim-ms commented 3 years ago

I haven't tested it yet, but possible issue could be the following (mentioned in Nuxtjs docs):

Only the primary module (in store/index.js) will receive the NuxtServerInit action. You'll need to chain your module actions from there.

If that's the case, my nuxtServerInit is being called from store/user.js ... maybe that's why I have the problem. @yuyusalim-ms can you confirm yours?

This is interesting but my nuxtServerInit is in store/index.js all along. Similar to your latest test, I’m getting null on hard refresh.

lupas commented 3 years ago

Hey @dosstx

If you have a look at https://github.com/lupas/nuxt-firebase-demo you can see that the user object can be accessed on the server in the middleware via store here. Maybe you can find your answer in that repo.

Did you follow Firebase Auth with SSR one-by-one? Do you have "firebase-admin" installed?

dosstx commented 3 years ago

@lupas

Did you follow Firebase Auth with SSR one-by-one? Do you have "firebase-admin" installed?

I believe I did it correctly, but I am often wrong...so I will be sure to look carefully again.

I do have firebase admin installed.

Someone pointed this out to me about the service worker being bypassed when doing "shift reload" , aka "hard refresh":

Shift-reload If you force-reload the page (shift-reload) it bypasses the service worker entirely. It'll be uncontrolled. This feature is in the spec, so it works in other service-worker-supporting browsers.

https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#shift-reload

That seems to be what is happening in my case. So, you confirm that it is not happening for you?

UPDATE: Not sure what happened, but everything seems to be working now.....didn't change a thing. Perhaps a cache issue? I don't know...but all works now....I'll leave this up a few more days if you don't mind in case I have issues again.

lupas commented 3 years ago

Seems like all works now, so closing this for now. Let me know if you run into this error again.

baradatbiu commented 2 years ago

This is interesting but my nuxtServerInit is in store/index.js all along. Similar to your latest test, I’m getting null on hard refresh.

@yuyusalim-ms I also have the same problem, did you manage to fix it?