DEV Community

Cover image for @nuxtjs/firebase social auth
Rodrigo Peña
Rodrigo Peña

Posted on • Edited on

@nuxtjs/firebase social auth

tl;dr: https://github.com/rodrigopv/nuxt-firebase-social-auth-demo

First, we set up a Nuxt.js project, in my case I used yarn create nuxt-app as shown on Nuxt getting started page

Nuxt project is ready

Then we install @nuxtjs/firebase by following their getting started guide.

Be sure to add your firebase keys (that you get from the firebase console after registering your web app on the project) to nuxt.config.js, along with the following parameters to activate the auth module:

nuxt.config.js:

  modules: [
    [
      '@nuxtjs/firebase',
      {
        config: {
          apiKey: 'FILL_THIS',
          authDomain: 'FILL_THIS',
          databaseURL: 'FILL_THIS',
          projectId: 'FILL_THIS',
          storageBucket: 'FILL_THIS',
          messagingSenderId: 'FILL_THIS',
          appId: 'FILL_THIS',
          measurementId: 'FILL_THIS'
        },
        services: {
          auth: {
            initialize: {
              onAuthStateChangedAction: 'onAuthStateChanged',
            },
            ssr: true,
          },
        }
      }
    ]
  ],
Enter fullscreen mode Exit fullscreen mode

So your file should look like this now:
Nuxt.config.js after setting up firebase module

To allow Nuxt to validate the logged user on server-side (so we don't break SSR), we must also activate a service worker:
yarn add --dev @nuxtjs/pwa

in nuxt.config.js add a 'pwa' key:

  pwa: {
    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',
    },
  }
Enter fullscreen mode Exit fullscreen mode

Now, we'll use the Vuex store to manage the authenticated user data:

create store/actions.js

export default {
  async nuxtServerInit({ dispatch }, ctx) {
    if (this.$fire.auth === null) {
      throw 'nuxtServerInit Example not working - this.$fire.auth cannot be accessed.'
    }

    if (ctx.$fire.auth === null) {
      throw 'nuxtServerInit Example not working - ctx.$fire.auth cannot be accessed.'
    }

    if (ctx.app.$fire.auth === null) {
      throw 'nuxtServerInit Example not working - ctx.$fire.auth cannot be accessed.'
    }

    // INFO -> Nuxt-fire Objects can be accessed in nuxtServerInit action via this.$fire___, ctx.$fire___ and ctx.app.$fire___'

    /** Get the VERIFIED authUser from the server */
    if (ctx.res && ctx.res.locals && ctx.res.locals.user) {
      const { allClaims: claims, ...authUser } = ctx.res.locals.user

      console.info(
        'Auth User verified on server-side. User: ',
        authUser,
        'Claims:',
        claims
      )

      await dispatch('onAuthStateChanged', {
        authUser,
        claims,
      })
    }
  },

  onAuthStateChanged({ commit }, { authUser, claims }) {
    if (!authUser) {
      commit('RESET_STORE')
      return
    }
    console.log('AuthStateChangedAction', authUser)
    commit('SET_AUTH_USER', authUser)
  },
}
Enter fullscreen mode Exit fullscreen mode

create store/getters.js

export default {
  isLoggedIn: (state) => {
    try {
      return state.authUser.id !== null
    } catch {
      return false
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

create store/index.js

export default {
  onAuthStateChanged({ commit }, authUser ) {
    if (!authUser) {
      commit('RESET_STORE')
      return
    }
    commit('SET_AUTH_USER', authUser)
  },
}
Enter fullscreen mode Exit fullscreen mode

create store/mutations.js

import initialState from './state'

export default {
  RESET_STORE: (state) => {
    Object.assign(state, initialState())
  },

  SET_AUTH_USER: (state, authUser) => {
    state.authUser = {
      uid: authUser.uid,
      email: authUser.email
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

create store/state.js

export default () => ({
  authUser: null
})
Enter fullscreen mode Exit fullscreen mode

Now our Vuex store should be ready to tell other components whether a user is logged in or not.

Let's create some basic pages for our example app:

  • Main page: anybody can access
  • Login page
  • User panel

Main page

Nothing fancy used Vuetify on the example project:
Main Page view

Login page

Same as the main page, but now with a sign in with Google button:
Sign In page view

Now we add the important methods to actually make the button work:

On pages/login.vue:
First, implement the method that will do the login logic:

methods: {
      async signInWithGoogle() {
        var provider = new this.$fireModule.auth.GoogleAuthProvider();
       // You can add or remove more scopes here provider.addScope('https://www.googleapis.com/auth/contacts.readonly');
        let authData = await this.$fire.auth.signInWithPopup(provider)
        this.$router.push('/profile')
      }
}
Enter fullscreen mode Exit fullscreen mode

Then we make sure we are calling this method on our button click event:

 <v-btn @click="signInWithGoogle" color="primary" class="ma-2">Sign in with Google</v-btn>
Enter fullscreen mode Exit fullscreen mode

And now our button should be working and opening a google sign in popup 🤩.

Now, we have to implement the /profile route that we specified above, which would be exclusive to users already logged in.

I just copied the main page but added a Vuex getter to retrieve the logged in user email:

User profile view

To implement a log out function we add the following method to our profile view page:

  methods: {
    async logout() {
      await this.$fire.auth.signOut()
      this.$router.push('/login')
    }
  }
Enter fullscreen mode Exit fullscreen mode

and we bind the button to use it:

<v-btn @click="logout">Logout</v-btn>
Enter fullscreen mode Exit fullscreen mode

Now, to protect our page from being accessed without being logged in, we create a middleware:

in middleware/auth.js:

export default function ({ store, redirect }) {
  if (!store.getters['isLoggedIn']) {
    return redirect('/login')
  }
}
Enter fullscreen mode Exit fullscreen mode

And then we protect the user profile page by adding

 export default {
    middleware: 'auth',
Enter fullscreen mode Exit fullscreen mode

to each page we want to make exclusively for logged in users.

What now

We just achieved a social login with Google. Firebase supports a lot of other social providers that might need some extra steps to be configured, but it works nice and easy.

About SSR

Server-side rendering stores Vuex server-side and pass it to the client every time the page is refreshed.

Being Firebase library designed for use at client-side, using more features such as vuexfire along SSR might be a major challenge that I haven't figured out yet.

What if I don't want to use SSR

This tutorial should work just fine, and you can omit the PWA / Service worker part.

Feedback

I'm no firebase expert and I'm just discovering more about how to use it along Nuxt. Feel free to make any comment or improvement to this guide.

Example repo

I've uploaded this example to github in case you want to clone the final result.

Github Repo: https://github.com/rodrigopv/nuxt-firebase-social-auth-demo

More info

Huge thanks to lupas (https://www.github.com/lupas/) for his work on @nuxtjs/firebase, along with the demo that has almost everything essential to know about how to use it: https://github.com/lupas/nuxt-firebase-demo

Top comments (5)

Collapse
 
lupas profile image
Pascal Luther

Thanks a lot Rodrigo for this great article! 👏👏👏

I added a link to this article to the links section of the nuxtjs/firebase documentation, hope that's alright with you.

Best regards and keep on Firenuxting! 🔥🔥🔥

Collapse
 
madhusudanbabar profile image
Krypton | Madhusudan Babar

Hey, i tried this, actually I've used this module before too but my problem is that when I'm trying to display the currentUser in the navbar, it actually displays after few seconds and i want to fetch the user details on server side, can you please help me with this!?

I can share the code if you wish,

Thanks 😊

Collapse
 
rodrigopv profile image
Rodrigo Peña

Hi,

Without analyzing too much the situation, sounds like you might want to add a loading screen before displaying the whole layout (so you don't show components where data is not available yet).

This tutorial allow you to have the user data on server side, but won't allow you to do authentified calls to firebase (such as retrieving firestore documents on behalf of the user).

That pattern is still being developed I think as Firebase has certain limitations when it comes to using it on SSR in the same way the browser does it (imagine the server doing many calls to firebase with different users tokens from the same IP, would look like suspicious activity).

What it makes sense to me is that vuex should be hydrated using Firebase Admin API on server-side instead of using the end user methods.

What I've read until now is that server should only know logged in user data (like this tutorial shows) to do basic routing decisions (such as sending the user to log in or show them the user profile view), and all the rest should be done on client-side as firebase is designed for.

Let me know if this make sense to you!

Collapse
 
fayaz profile image
Fayaz Ahmed

I am using this with target: "static" and ssr in the auth service is not defined, the login works as well as the auth state is changed, but it won't redirect to the /profile url. Any idea why?

Collapse
 
nabilfr profile image
NabilFr • Edited

After sign In the redirection to profile doesn't work.

Edit: Nevermind it's set on the google api platform, waiting for ma uri to update. I have a mismatch uri redirection.