【问题标题】:Storing Firebase accessToken in Pinia store - Vue3在 Pinia 商店中存储 Firebase accessToken - Vue3
【发布时间】:2023-01-24 02:27:44
【问题描述】:

我是 Vue 的新手,这是我第一次使用 Pinia。我正在关注 this guide 以设置 Firebase、Pinia 和 Axios。我正在构建的应用程序使用 FirebaseUI 通过电子邮件链接让用户登录 - 这一切都发生在下面的 LoginPage 组件中:

(请忽略所有错误类型的变量/函数——我只是想首先让它工作)

<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>

当用户成功登录时,应用程序更新 Pinia 商店用户对象,使用 AuthResultsignInSuccessWithAuthResult 函数返回对象。调试器时,我可以看到正在存储的对象如下所示:

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

IE。 accessToken 正在被存储。用户存储如下:

import { defineStore } from 'pinia'

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

在应用程序中,我设置了一个 axios 拦截器,它将 accessToken 附加到应用程序发出的任何 Axios 请求:

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

此时尝试从用户存储中检索 accessToken 时,它不见了。用户对象的大多数(如果不是全部)其他属性仍然存在,但访问令牌不存在,因此我很确定我正在正确使用商店:

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

任何人都可以解释我哪里出了问题,以及为什么 accessToken 从商店中删除?在我看来,我好像正确地使用了 Pinia 商店,而且我很确定拦截器也是正确的。但是,我可能会以错误的方式存储访问令牌。对于如何使用 Vue 正确设置 Firebase 身份验证的任何帮助/建议,我将不胜感激。

编辑为在拦截器内部调试时包含用户存储的值。

【问题讨论】:

  • 您需要缩小问题范围,它包含不相关的部分。没有解释为什么一个属性会是未定义的,除非你明确地设置它。如果user 为空,user.accessToken 将抛出错误。如果等于authResult,并且有accessToken,则定义user.accessTokenstore.user = authResult - 这是一个错误。不应直接修改存储状态,只能在操作中修改。然后这可以在开发工具中看到。 “当调试器时,我可以看到”——哪个调试器?我相信这个问题在这一点上是不可重现的。
  • 感谢您的评论@EstusFlask - 我只添加了适用于该问题的代码部分。在我的应用程序中,我没有在其他任何地方更新用户 pinia 商店,仅在上面的部分中。我相信我能够直接从组件内部修改状态 - pinia.vuejs.org/core-concepts/state.html#accessing-the-state(无论看起来多么奇怪)。正如你所说,'如果它等于 authResult,并且有 accessToken,user.accessToken 将被定义' - 我完全同意,但是当我访问该对象时,它是未定义的,但用户名、电子邮件等仍然存在。
  • 您可以在外部修改状态,但您不应该这样做,这是一种不好的做法,因为它无法跟踪到特定的操作。它尚未在 Pinia 中强制执行,但这种做法是从 Vuex 继承的。请提供一种重现它的方法。目前唯一的解释是没有调试好,不知道具体是怎么调试的。尽可能使用 Pinia devtools

标签: firebase vue.js axios vuejs3 pinia


【解决方案1】:

看起来 accessToken 可能在 userStore.user.user.accessToken 中?

【讨论】:

  • 我更新了我的问题以包含值 userStore.user.useraccessToken对象上不再存在
【解决方案2】:

我刚刚结束了与您所处的同一场战斗……IMO 有很多方法可以配置此设置……这类似于您可能在一个地方使用回调,而在另一个地方使用异步等待的原因,这取决于您的项目结构体。

这是一个简单的示例,可以帮助您阐明它。

第一的创建一个 firebase 文件来保存配置,将其放在您的组织习惯要求您放置的位置。请记住,以便我们稍后使用它。

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);

第二 - userStore

用户商店负责跑腿工作。当我们想从我们的 ui 与 userauth 交互时,我们将使用这些操作。

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 在 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>

注册将是相似的

  <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>

【讨论】:

    猜你喜欢
    • 2022-07-18
    • 2020-09-10
    • 2022-08-17
    • 2022-07-10
    • 2022-07-26
    • 2022-10-01
    • 2022-08-17
    • 2022-06-30
    • 1970-01-01
    相关资源
    最近更新 更多