PWA

사용자 인증 - Google 계정 연결

그랜파 개발자 2024. 8. 7. 19:22

46. Google 계정 연결

구글 계정으로 로그인할 수 있도록 합니다. 그러기 위해서는 먼저 구글 계정을 연결해야 합니다. 구글 계정 연결은 구글 계정의 uid를 my-auth 계정과 연결된 계정 정보에 uid를 저장하는 것입니다. 물론 구글 계정과 연결하려면 구글에 계정이 있어야 합니다.

Google 계정 연결은 다음과 같은 시퀀스를 가집니다.

  1. 로그인한 후 계정 정보 페이지에 접속합니다.
  2. 계정 정보 페이지에서 구글 계정 연결을 클릭하면 my-auth 앱은 구글 계정으로 구글에 로그인을 합니다.
  3. 구글에 로그인을 성공하면 user 객체를 돌려 받습니다.
  4. 구글에서 돌려받은 user 객체의 uid를 현재 로그인된 계정의 uids에 추가합니다.

위와 같이 하는 이유는 이메일/비밀번호로 계정을 만든 회원이 구글 로그인으로도 로그인을 하도록 하고, 이 두 계정이 같은 계정이란 것을 확인하기 위함입니다. 계정 연결을 한 후에는 구글 로그인으로도 로그인이 가능해집니다.

로그인한 후에 계정 정보 링크가 나타나고, 이를 통해 계정 정보 페이지로 이동합니다. 계정 정보 페이지에는 회원의 기본 정보를 보여주고, 구글 계정 연동 버튼이 있습니다. 이것을 누르면 계정 연동이 됩니다.

 

ChatGPT에게 물었습니다.

현재 로그인 된 user의 uids에 uid를 추가하는 방법은?

How to add a uid to the uids of the currently logged in user?

To add a new uid to the uids array field of the currently logged-in user in Firestore, you can follow these steps: Firestore에 현재 로그인한 사용자의 uids 배열 필드에 새 uid를 추가하려면 다음 단계를 따르세요.

  1. Ensure Firebase is Initialized: Initialize Firebase and Firestore in your project.
    Firebase가 초기화되었는지 확인: 프로젝트에서 Firebase 및 Firestore를 초기화합니다.
  2. Get the Current User: Retrieve the currently logged-in user using Firebase Auth.
    현재 사용자 가져오기: Firebase 인증을 사용하여 현재 로그인된 사용자를 검색합니다.
  3. Update the uids Array: Use Firestore's arrayUnion method to add a new uid to the uids array field of the user's document.
    uids 배열 업데이트: Firestore의 arrayUnion 메서드를 사용하여 사용자 문서의 uids 배열 필드에 새 uid를 추가합니다.

Here's a step-by-step implementation:
단계별 구현은 다음과 같습니다.

Step-by-Step Implementation

1. Initialize Firebase and Firestore 

 

Make sure Firebase and Firestore are properly initialized.
Firebase와 Firestore가 올바르게 초기화되었는지 확인하세요.

 

firebase.js

// firebase.js
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';

const firebaseConfig = {
  apiKey: 'your-api-key',
  authDomain: 'your-auth-domain',
  projectId: 'your-project-id',
  storageBucket: 'your-storage-bucket',
  messagingSenderId: 'your-messaging-sender-id',
  appId: 'your-app-id'
};

firebase.initializeApp(firebaseConfig);

const auth = firebase.auth();
const db = firebase.firestore();

export { auth, db };

2. Vuex Store to Manage User State

Define a Vuex store to manage the user's state, including updating the uids array.
uids 배열 업데이트를 포함하여 사용자 상태를 관리하기 위해 Vuex Store를 정의합니다.

 

store.js

// store.js
import Vue from 'vue';
import Vuex from 'vuex';
import { auth, db } from './firebase';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    user: null,
    authReady: false
  },
  mutations: {
    setUser(state, user) {
      state.user = user;
    },
    setAuthReady(state, isReady) {
      state.authReady = isReady;
    }
  },
  actions: {
    async fetchUser({ commit }) {
      auth.onAuthStateChanged(async (firebaseUser) => {
        if (firebaseUser) {
          try {
            const userDoc = await db.collection('users').doc(firebaseUser.uid).get();
            const userData = userDoc.exists ? userDoc.data() : {};
            const customUser = {
              uid: firebaseUser.uid,
              email: firebaseUser.email,
              displayName: firebaseUser.displayName,
              photoURL: firebaseUser.photoURL,
              ...userData // Add additional fields from Firestore
            };
            commit('setUser', customUser);
          } catch (error) {
            console.error('Error fetching user data:', error);
          }
        } else {
          commit('setUser', null);
        }
        commit('setAuthReady', true);
      });
    },
    async addUidToUser({ state }, newUid) {
      if (state.user && state.user.uid) {
        try {
          await db.collection('users').doc(state.user.uid).update({
            uids: firebase.firestore.FieldValue.arrayUnion(newUid)
          });
          // Update the local state if needed
          state.user.uids = [...(state.user.uids || []), newUid];
        } catch (error) {
          console.error('Error adding UID to user:', error);
        }
      }
    }
  },
  getters: {
    user: state => state.user,
    authReady: state => state.authReady
  }
});

3. Component to Add a UID to the uids Array

Create a Vue component that allows adding a new uid to the uids array of the current user.
현재 사용자의 uids 배열에 새 uid를 추가할 수 있는 Vue 구성 요소를 만듭니다.

 

AddUid.vue

// AddUid.vue
<template>
  <div v-if="authReady">
    <div v-if="user">
      <h1>Welcome, {{ user.displayName || user.email }}</h1>
      <input v-model="newUid" placeholder="Enter UID to add">
      <button @click="addUid">Add UID</button>
    </div>
    <div v-else>
      <p>No user is signed in.</p>
    </div>
  </div>
  <div v-else>
    <p>Loading...</p>
  </div>
</template>

<script>
import { mapGetters } from 'vuex';

export default {
  data() {
    return {
      newUid: ''
    };
  },
  computed: {
    ...mapGetters(['user', 'authReady'])
  },
  methods: {
    addUid() {
      if (this.newUid) {
        this.$store.dispatch('addUidToUser', this.newUid);
        this.newUid = ''; // Clear the input field
      }
    }
  },
  created() {
    this.$store.dispatch('fetchUser');
  }
};
</script>

Explanation 설명

1. Firebase Initialization:

    The firebase.js file initializes Firebase and exports the auth and db instances.
firebase.js 파일은 Firebase를 초기화하고 auth 및 db 인스턴스를 내보냅니다.

 

2. Vuex Store:

  1. State: Holds the custom user object and a flag (authReady) to indicate if the authentication state has been checked.
    State: 사용자 정의 사용자 개체와 인증 상태가 확인되었는지 나타내는 플래그(authReady)를 보유합니다.
  2. Mutations: setUser to update the custom user object, setAuthReady to update the authentication readiness flag.
    Mutations: 사용자 정의 사용자 개체를 업데이트하는 setUser, 인증 준비 플래그를 업데이트하는 setAuthReady.
  3. Actions:
    1. fetchUser listens for authentication state changes using auth.onAuthStateChanged. If a user is signed in, it fetches additional user data from Firestore and commits the combined object to the store.
      fetchUser는 auth.onAuthStateChanged를 사용하여 인증 상태 변경을 수신합니다. 사용자가 로그인하면 Firestore에서 추가 사용자 데이터를 가져오고 결합된 객체를 저장소에 커밋합니다.
    2. addUidToUser updates the uids array field of the current user's document in Firestore and updates the local state if needed.
      addUidToUser는 Firestore에서 현재 사용자 문서의 uids 배열 필드를 업데이트하고 필요한 경우 로컬 상태를 업데이트합니다.
  4. Getters: user to access the custom user object and authReady to check if authentication state is ready.
    Getters: 사용자 정의 사용자 객체에 액세스하기 위한 user 및 인증 상태가 준비되었는지 확인하기 위한 authReady.

3. AddUid Component:

  1. Uses the authReady state to display a loading message until the authentication state is determined.
    'authReady' 상태를 사용하여 인증 상태가 결정될 때까지 로딩 메시지를 표시합니다.
  2. Displays user information and provides an input field to enter a new uid.
    사용자 정보를 표시하고 새로운 'uid'를 입력할 수 있는 입력 필드를 제공합니다
  3. Dispatches the addUidToUser action when the button is clicked to add the new uid to the uids array field in Firestore.
    Firestore의 uids 배열 필드에 새 uid를 추가하기 위해 버튼을 클릭하면 addUidToUser 작업을 전달합니다.
  4. Dispatches fetchUser action when created to initialize the authentication state check.
    인증 상태 확인을 초기화하기 위해 생성되면 fetchUser 작업을 전달합니다.

By following these steps, you can add a new uid to the uids array field of the currently logged-in user in Firestore, and manage this data within your Vue application using Vuex.

다음 단계에 따라 Firestore에 현재 로그인한 사용자의 'uids' 배열 필드에 새 'uid'를 추가하고 Vuex를 사용하여 Vue 애플리케이션 내에서 이 데이터를 관리할 수 있습니다.

ChatGPT가 시키는 대로 했습니다.

store.js의 async addUidToUser({ state }, newUid)를 async addUidToUser({ state }, newUid)로 변경하여 추가하였습니다.

 

오류가 발생합니다.

ChatGPT에게 물었습니다.

Cannot read properties of undefined (reading 'arrayUnion') 해결방법은?

 

The error "Cannot read properties of undefined (reading 'arrayUnion')" typically occurs when the Firestore SDK has not been properly imported or initialized. The arrayUnion method is part of the firebase.firestore.FieldValue object, and if firebase is not correctly set up, you will encounter this issue.

 

"정의되지 않은 속성을 읽을 수 없습니다('arrayUnion' 읽기)" 오류는 일반적으로 Firestore SDK를 제대로 가져오거나 초기화하지 않은 경우 발생합니다. 'arrayUnion' 메서드는 'firebase.firestore.FieldValue' 개체의 일부이며, 'firebase'가 올바르게 설정되지 않은 경우 이 문제가 발생합니다.

 

To resolve this error, ensure the following:
이 오류를 해결하려면 다음을 확인하세요.

  1. Correct Import of Firebase and Firestore: Ensure that Firebase and Firestore are properly imported in your project.
    Firebase 및 Firestore를 올바르게 가져오기: 프로젝트에서 Firebase 및 Firestore를 올바르게 가져왔는지 확인하세요.
  2. Proper Initialization of Firebase: Ensure that Firebase is correctly initialized with your project's configuration.
    Firebase의 적절한 초기화: 프로젝트 구성에 따라 Firebase가 올바르게 초기화되었는지 확인하세요.
  3. Correct Usage of firebase.firestore.FieldValue.arrayUnion: Ensure that you are using the arrayUnion method correctly.
    firebase.firestore.FieldValue.arrayUnion의 올바른 사용법: arrayUnion 메서드를 올바르게 사용하고 있는지 확인하세요.

Here’s a complete example demonstrating the correct setup and usage:
다음은 올바른 설정과 사용법을 보여주는 전체 예입니다.

Step-by-Step Solution

1. Initialize Firebase and Firestore

 

Make sure Firebase and Firestore are initialized correctly. Here’s an example firebase.js file:
Firebase와 Firestore가 올바르게 초기화되었는지 확인하세요. 다음은 firebase.js 파일의 예입니다.

// firebase.js
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';

// Your web app's Firebase configuration
const firebaseConfig = {
  apiKey: 'your-api-key',
  authDomain: 'your-auth-domain',
  projectId: 'your-project-id',
  storageBucket: 'your-storage-bucket',
  messagingSenderId: 'your-messaging-sender-id',
  appId: 'your-app-id'
};

// Initialize Firebase
firebase.initializeApp(firebaseConfig);

// Initialize Firestore
const db = firebase.firestore();

export { db, firebase };

2. Update the Document with arrayUnion

Ensure that you are importing db and firebase correctly in your component or Vuex store, and using firebase.firestore.FieldValue.arrayUnion properly.
구성 요소 또는 Vuex 저장소에서 db 및 firebase를 올바르게 가져오고 ‘firebase.firestore.FieldValue.arrayUnion’을 올바르게 사용하고 있는지 확인하세요.

// ExampleComponent.vue or store.js
import { db, firebase } from './firebase'; // Adjust the import path accordingly

const userDocRef = db.collection('users').doc('user123');

async function updateUserProfile() {
  try {
    await userDocRef.update({
      name: 'Updated Name',
      email: 'updated.email@example.com',
      // Add a new UID to the uids array field
      uids: firebase.firestore.FieldValue.arrayUnion('new-uid')
    });
    console.log('User profile updated successfully!');
  } catch (error) {
    console.error('Error updating user profile:', error);
  }
}

// Call the function to update the user profile
updateUserProfile();

Common Issues and Solutions

1. Incorrect Import of firebase or firebase/firestore: Ensure that the Firestore package is correctly imported and initialized.
   firebase 또는 firebase/firestore를 잘못 가져옴: Firestore 패키지를 올바르게 가져오고 초기화했는지 확인하세요.

import firebase from 'firebase/app';
import 'firebase/firestore';

 

2. Ensure Firebase is Initialized Before Using Firestore: Firebase must be initialized with your configuration before you can use Firestore methods.
    Firestore를 사용하기 전에 Firebase가 초기화되었는지 확인하세요: Firestore 메서드를 사용하려면 먼저 구성을 사용하여 Firebase를 초기화해야 합니다.

firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();

 

3. Check Firebase Configuration: Ensure that the Firebase configuration object contains the correct project details.
Firebase 구성 객체에 올바른 프로젝트 세부정보가 포함되어 있는지 확인하세요.

Complete Example in a Vue Component

Here is a full example within a Vue component that ensures everything is set up correctly:
다음은 모든 것이 올바르게 설정되었는지 확인하는 Vue 구성 요소 내의 전체 예입니다.

<template>
  <div>
    <input v-model="newUid" placeholder="Enter UID to add">
    <button @click="addUid">Add UID</button>
  </div>
</template>

<script>
import { db, firebase } from './firebase'; // Ensure correct import path

export default {
  data() {
    return {
      newUid: ''
    };
  },
  methods: {
    async addUid() {
      if (this.newUid) {
        const userDocRef = db.collection('users').doc('user123');
        try {
          await userDocRef.update({
            uids: firebase.firestore.FieldValue.arrayUnion(this.newUid)
          });
          console.log('UID added successfully!');
        } catch (error) {
          console.error('Error adding UID:', error);
        }
      }
    }
  }
};
</script>

 

By ensuring correct imports, initialization, and usage of the Firestore methods, you should be able to resolve the error and successfully update the document in Firestore.
Firestore 메서드를 올바르게 가져오고 초기화하고 사용하면 오류를 해결하고 Firestore에서 문서를 성공적으로 업데이트할 수 있습니다.

오류를 해결하기 위하여 다음과 같이 합니다.

  1. firebase.js에서 export { db, firebase } 하기
  2. store/index.js에서 import { db, firebase } from './firebase'; // Ensure correct import path 하기

Source Code

src/views/UserInfo.vue

<!-- src/views/UserInfo.vue -->
<template>
  <v-container>
    <v-card v-if="user">
      <v-card-title>User Information</v-card-title>
      <v-card-text>
        <p><strong>이메일:</strong> {{ user.email }}</p>
        <p><strong>이름:</strong> {{ user.name }}</p>
        <p><strong>id:</strong> {{ user.id }}</p>
      </v-card-text>
      <v-card-text>
        <v-btn color="red" @click="addGoogleAccount" dark>  
          <v-icon left>mdi-google</v-icon>
          Google 계정 연동
        </v-btn>
      </v-card-text>
    </v-card>
    <v-alert v-else type="info">No user is logged in.</v-alert>
  </v-container>
</template>

<script>
import { mapActions, mapGetters } from 'vuex';

export default {
  computed: {
    ...mapGetters(['user'])
  },
  methods: {
    ...mapActions(['addGoogleUid']),
    async addGoogleAccount() {
      await this.addGoogleUid();
    }
  }
};
</script>

src/store/index.js

Copy// src/store/index.js

import Vue from 'vue';
import Vuex from 'vuex';
import { db, firebase, auth, googleProvider } from '@/firebase';
import router from '@/router';  // Vue Router import

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    user: null,
    users: [],
    isLoading: false,
    error: null
  },
  mutations: {
    setUser(state, user) {
      state.user = user;
    },
    setUsers(state, users) {
      state.users = users;
      state.isLoading = false;
    },
    setError(state, error) {
      state.error = error;
      state.isLoading = false;
    },
    setLoading(state, isLoading) {
      state.isLoading = isLoading;
    }
  },
  actions: {
    async fetchUsers({ commit }) {
      commit('setLoading', true);
      try {
        const usersSnapshot = await db.collection('users').get();
        const users = usersSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
        console.log(users);
        commit('setUsers', users);
      } catch (error) {
        commit('setError', error.message);
      }
    },
    async addUser({ dispatch, commit }, user) {
      try {
        await db.collection('users').add(user)
        .then(newUser => {

          // 로그인 설정
          user.id = newUser.id;
          console.log('addUser :', user);
          commit('setUser', user);
        });

        dispatch('fetchUsers');
      } catch (error) {
        console.error('Error adding user:', error);
      }
    },
    async deleteUser({ dispatch }, userId) {
      try {
        await db.collection('users').doc(userId).delete();
        dispatch('fetchUsers');
      } catch (error) {
        console.error('Error deleting user:', error);
      }
    },
    async updateUser({ dispatch }, { userId, user }) {
      try {
        await db.collection('users').doc(userId).update(user);
        dispatch('fetchUsers');
      } catch (error) {
        console.error('Error updating user:', error);
      }
    },
    async login({ commit, dispatch }, { email, password }) {
      try {
        const { user } = await auth.signInWithEmailAndPassword(email, password);;
        // 웹앱의 계정 정보를 가져와 로그인 설정을 한다.
        dispatch('fetchUserWithUid', {uid: user.uid});       
        router.push("/");   // home으로
      } catch (error) {
        commit('setError', error.message);
      }
    },
    async fetchUserWithUid({ commit }, {uid}) {
      //console.log(uid);
      try {
        const querySnapshot = await db.collection('users').where('uids', 'array-contains', uid).get();
        //const user = querySnapshot.docs.map(doc => doc.data());
        const user = querySnapshot.docs. map(doc => ({ id: doc.id, ...doc.data() }));
        //console.log('Matching user:', user[0]);
        // 로그인 설정을 한다.
        commit('setUser', user[0]);
      } catch (error) {
        console.error('Error fetching user:', error);
      }            
    },
    async register({ commit, dispatch }, { email, password, name }) {
      try {
        const { user } = await auth.createUserWithEmailAndPassword(email, password, name);
        // 웹앱에 계정을 만든다.
        const newUser = {
          email: email,
          name: name,
          uids: [user.uid]
        };
        // newUser 객체가 추가됨
        // dispatch('addUser', { user: newUser });
        // newUser 계정 정보가 추가됨
        dispatch('addUser', newUser);

        router.push("/");   // home으로        
      } catch (error) {
        console.log(error);
        commit('setError', error.message);
      }
    },
    async addUidToUser({ state }, newUid) {      
      //console.log('user.id:', state.user.id);
      if (state.user) {
        try {
          await db.collection('users').doc(state.user.id).update({
            uids: firebase.firestore.FieldValue.arrayUnion(newUid)
          });
          // Update the local state if neededa
          state.user.uids = [...(state.user.uids || []), newUid];
        } catch (error) {
          console.error('Error adding UID to user:', error);
        }
      }
    },
    async addGoogleUid({ commit, dispatch }) {
      try {
        const { user } = await auth.signInWithPopup(googleProvider);
        try {
          //console.log("1", user.id);
          dispatch('addUidToUser', user.uid);
        } catch (error) {
          commit('setError','Error adding google uid');
        }
      } catch (error) {
        commit('setError', error.message);
      }
    },
    async googleLogin({ commit, getters }) {
      try {
        const { user } = await auth.signInWithPopup(googleProvider);
        try {
          const myUser = getters.getUserByUid(user.uid);
          if (!myUser) {
            commit('setUser', myUser);
            console.log(myUser);
            router.push("/");   // home으로
          } else {
            console.log('User already exists');
          }
        } catch (error) {
          console.error('Error adding user:', error);
        }
      } catch (error) {
        commit('setError', error.message);
      }
    },
    async logout({ commit }) {
      await auth.signOut();
      commit('setUser', null);
    },
    setUser({ commit }, user) {
      commit('setUser', user);
    }
  },
  getters: {
    user: state => state.user,
    users: state => state.users,
    isLoading: state => state.isLoading,
    error: state => state.error,
    getUserByUid: (state) => (uid) => {
      return state.users.find(user => user.uids && user.uids.includes(uid));
    }
  }
});