skip to Main Content

I’m fairly new to Vue and it’s my first time using Pinia. I’m following this guide to set up Firebase, Pinia and Axios. The app I’m building uses FirebaseUI to sign a user in, via an email link – this all happens in the LoginPage component below:

(Please ignore all incorrectly types variables/functions – I’m just trying to get this working in the first place)

<script setup lang="ts">
import { onMounted } from "vue";
import { EmailAuthProvider } from "firebase/auth";
import { auth } from "firebaseui";
import { auth as firebaseAuth } from "../firebase/config";
import { useUserStore } from "../stores/user"

onMounted(async () => {
  const uiConfig: auth.Config = {
    signInSuccessUrl: "/",
    signInOptions: [
      {
        provider: EmailAuthProvider.PROVIDER_ID,
        signInMethod: EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD,
        forceSameDevice: true,
      },
    ],
    callbacks: {
      signInSuccessWithAuthResult: function (authResult) {
        const store = useUserStore();
        store.user = authResult;
        return true;
      },
    },
  };
  const ui = new auth.AuthUI(firebaseAuth);
  ui.start("#firebaseui-auth-container", uiConfig);
});
</script>
<template>
  <div id="firebaseui-auth-container"></div>
</template>

When the user successfully signs in, the app updates the Pinia store user object, with the AuthResult return object from the signInSuccessWithAuthResult function. When debugger, I can see that the object being stored looks like the following:

{
    additionalUserInfo: {...}
    operationType: "signIn"
    user: {
        accessToken: "eyJhbGciOiJSUzI1N..."
        auth: {...}
        displayName: null
        ...
    }
}

I.e. the accessToken is being stored. The user store is below:

import { defineStore } from 'pinia'

export const useUserStore = defineStore("userStore", {
  state: () => ({
    user: null as any
  }),
  getters: {
    getUser(state) {
      return state.user
    }
  }
})

In the app I have set up an axios interceptor, that appends the accessToken to any Axios request made by the app:

axiosInstance.interceptors.request.use((config) => {
  const userStore = useUserStore();
  
  if (userStore) {
    debugger;
    // accessToken is undefined
    config.headers.Authorization = 'Bearer ' + userStore.user.user.accessToken;
  }
  return config;
});

When attempting the retrieve the accessToken from the user store at this point, it’s gone. Most (if not all) of the other properties from the user object still exist, but not the access token, therefore I’m pretty sure I’m using the store correctly:

{
    additionalUserInfo: {...}
    credential: null
    operationType: "signIn"
    user: {
        // accessToken is gone
        apiKey: "..."
        appName: "[DEFAULT]"
        email: "..."
        emailVerified: true
        ....
    }
}

Can anybody explain where I’m going wrong with this, and why the accessToken is being removed from the store? It looks to me as though I’m using the Pinia store correctly, and I’m pretty sure that the interceptor is also correct. However it’s likely that I’m going about storing the access token in the wrong way. I’d appreciate any help/advice about how to setup Firebase authentication correctly with Vue.

Edited to include value of the user store when debugging inside the interceptor.

2

Answers


  1. It looks like accessToken might be in userStore.user.user.accessToken?

    Login or Signup to reply.
  2. Im just finishing the same battle that you are in… IMO there are many ways that this setup can be configured… This is similar to why you might use callbacks in one place, and async await in another it depends on your project structure.

    Heres a simple example that might help you clarify it.

    first
    create a firebase file to hold the config put this where ever your organization habits tells you to put it. Just remember so we can use it later.

    import { initializeApp } from "firebase/app";
    import { getAuth } from "firebase/auth";
    
    const firebaseConfig = {
      apiKey: "",
      authDomain: "",
      projectId: "",
      storageBucket: "",
      messagingSenderId: "",
      appId: "",
      measurementId: "",
    };
    
    // Initialize Firebase
    const app = initializeApp(firebaseConfig);
    
    //initialize firebase auth
    export const auth = getAuth(app);
    

    Second – userStore

    The user store does the legwork. We will use the actions when we want to interact with userauth from our ui.

    import {
      createUserWithEmailAndPassword,
      onAuthStateChanged,
      signInWithEmailAndPassword,
      signOut,
    } from "firebase/auth";
    
    import { auth } from "../firebase"; // the file we made above
    
    import router from "../router";
    
    export const useUserStore = defineStore("userStore", {
      state: () => ({
        userData: null,
        loadingUser: false,
        loadingSession: false,
      }),
      actions: {
        async registerUser(email, password) {
          this.loadingUser = true;
          try {
            const { user } = await createUserWithEmailAndPassword(
              auth,
              email,
              password
            );
            this.userData = { email: user.email, uid: user.uid };
            router.push("/");
          } catch (error) {
            console.log(error);
          } finally {
            this.loadingUser = false;
          }
        },
        async loginUser(email, password) {
          this.loadingUser = true;
          try {
            const { user } = await signInWithEmailAndPassword(
              auth,
              email,
              password
            );
            this.userData = { email: user.email, uid: user.uid };
            router.push("/");
          } catch (error) {
            console.log(error);
          } finally {
            this.loadingUser = false;
          }
        },
        async logOutUser() {
          try {
            await signOut(auth);
            this.userData = null;
            router.push("/login");
          } catch (error) {
            console.log(error);
          }
        },
        currentUser() {
          return new Promise((resolve, reject) => {
            const unsuscribe = onAuthStateChanged(
              auth,
              (user) => {
                if (user) {
                  this.userData = { email: user.email, password: user.password };
                } else {
                  this.userData = null;
                }
                resolve(user);
              },
              (e) => reject(e)
            );
            unsuscribe();
          });
        },
      },
    });
    

    *** step3 setup the login / reg components in vue. ***

      <div>
        <form @submit.prevent="login">
          <label>
            Email:
            <input type="email" v-model="email" required />
          </label>
          <br />
          <label>
            Password:
            <input type="password" v-model="password" required />
          </label>
          <br />
          <button type="submit">Login</button>
        </form>
      </div>
    </template>
    
    <script>
    import { useUserStore } from "../stores/user";
    export default {
      data() {
        return {
          email: "",
          password: "",
        };
      },
      methods: {
        async login() {
          try {
            await this.userStore.loginUser(this.email, this.password);  // 
          } catch (error) {
            console.error(error);
          }
        },
      },
    
    // because of below setup you can access this.userStore() singleton 
    
      setup() {  
        const userStore = useUserStore();
        return {
          userStore,
        };
      },
    };
    </script>
    

    register is going to be simailar

      <div>
        <form @submit.prevent="register">
          <label>
            Email:
    
            <input type="email" v-model="email" required />
          </label>
          <br />
          <label>
            Password:
            <input type="password" v-model="password" required />
          </label>
          <br />
          <button type="submit">Register</button>
        </form>
      </div>
    </template>
    <script>
    import { useUserStore } from "../stores/user";
    export default {
      data() {
        return {
          email: "",
          password: "",
        };
      },
      methods: {
        async register() {
          try {
            await this.userStore.registerUser(this.email, this.password);
          } catch (error) {
            console.error(error);
          }
        },
      },
      setup() {
        const userStore = useUserStore();
        return {
          userStore,
        };
      },
    };
    </script>
    

    now whenever you want to access the user it is in userStore.userData

    if you dont have the userStore up yet just use the useUserStore() method and access it the same way you do from the setup in login / register view

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search