그랜파 개발자의 프론트엔드 공부-Vue

Vue로 PWA 개발, 프론트엔드 공부 my-pwa-app의 자동 로그인과 상태 관리

그랜파 개발자 2024. 12. 7. 15:59

Vue로 PWA 개발 - 그랜파 개발자.

 

로그아웃하지 않은 상태에서 my-pwa-app를 종료하였다면 다음에 접속할 때는 자동으로 로그인하도록 합니다.

그리고 로그인 상태에 따라 메뉴 항목이 다르게 보이도록 만들어 봅시다.

1. 로그인 전

그림 35-1

2. 로그인 후

그림 35-2

3. 자동 로그인

로그인 인증 상태에 따른 자동 로그인 기능 구현을 위하여 onAuthStateChanged를 사용합니다.
onAuthStateChanged는 Firebase Authentication에서 제공하는 메서드로, 사용자의 인증 상태 변화를 실시간으로 감지하는 역할을 합니다. 사용자가 로그인하거나 로그아웃할 때, 또는 앱이 처음 로드될 때 트리거됩니다.

이 메서드는 애플리케이션의 인증 상태를 지속적으로 모니터링하는 데 유용합니다.

src/main.js

// src/main.js
import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import store from './store'
import vuetify from './plugins/vuetify'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  vuetify,
  render: h => h(App),
  created() {
    // Set up Firebase auth state change listener
    const { dispatch } = this.$store;
    // Initialize Firebase authentication to check for the logged-in user
    dispatch('auth/initializeAuth');
  }
}).$mount('#app')

src/store/modules/auth.js

// src/store/modules/auth.js
// -- 앱을 시작하면 자동 로그인을 설정한다.
  async initializeAuth({ commit }) {
    onAuthStateChanged(auth, (user) => {
      if (user) {
        // user.uid로 웹앱의 firestore DB에서 계정 정보를 가져온다.
        commit("setUser", user);
      } else {
        commit("setUser", null);
      }
    });
  },

 

앱이 열릴 때 onAuthStateChanged를 호출하여 로그인 여부를 store의 state 변수 user에 설정을 합니다. 로그인을 성공하였다면 store의 state 변수 user에는 UserCredential 객체가 저장될 것입니다.

4. 로그인 상태 관리

my-pwa-app에는 현재 Home, Post, Login, Register, About, Contact의 6개 메뉴 항목을 가지고 있습니다. 회원이 로그인 상태가 아니라면 메뉴 항목 6개 모두를 보이고, 회원이 로그인 상태라면 Login, Register는 보일 필요가 없습니다.

로그인 상태에 따라 메뉴 항목이 다르게 보이도록 만들어 봅시다.

src/App.vue

<!-- src/App.vue -->
<template>
  <v-app>
    <!-- App Bar -->
    <v-app-bar color="primary" dark app>
      <v-app-bar-nav-icon @click="toggleDrawer" />
      <v-toolbar-title>My App</v-toolbar-title>
      <v-spacer></v-spacer>
      <div @click="doLogout" v-if="user" style="cursor: pointer">
        <v-icon left class="ml-1">mdi-logout</v-icon>
      </div>
    </v-app-bar>

    <!-- Navigation Drawer -->
    <v-navigation-drawer v-model="drawer" app>
      <v-list>
        <v-list-item-group>
          <v-list-item v-for="(item, index) in GetMenuItems"
            :key="index"
            @click="$router.push(item.path).catch(() => { drawer = !drawer });">
            <v-list-item-icon>
              <v-icon>{{ item.icon }}</v-icon>
            </v-list-item-icon>
            <v-list-item-content>
              <v-list-item-title>{{ item.title }}</v-list-item-title>
            </v-list-item-content>
          </v-list-item>
        </v-list-item-group>
      </v-list>
    </v-navigation-drawer>

    <!-- Main Content -->
    <v-main>
      <v-container>
        <router-view /> <!-- 라우터가 여기에 컴포넌트를 렌더링 -->
      </v-container>
    </v-main>

    <!-- Footer -->
    <v-footer app color="secondary" dark>
      <v-container>
        <p>&copy; {{ new Date().getFullYear() }} My App. All rights reserved.</p>
      </v-container>
    </v-footer>
  </v-app>
</template>

<script>
import { mapState, mapActions } from 'vuex';
import router from '@/router';

export default {
  data() {
    return {
      drawer: false,
      menuItems: [

      ],
    };
  },
  computed: {
    ...mapState('auth', ['user']),

    // 로그인 여부에 따라 다르게 탐색서랍과 툴바 메뉴명 항목 배열 반환
    GetMenuItems() {
      if(this.user) {
        return [
          { title: 'Home', icon: 'mdi-home', path: '/' },
          { title: 'Post', icon: 'mdi-pencil', path: '/post' },
          { title: 'About', icon: 'mdi-information', path: '/about' },
          { title: 'Contact', icon: 'mdi-phone', path: '/contact' },
        ]
      } else {
        return [
          { title: 'Home', icon: 'mdi-home', path: '/' },
          { title: 'Post', icon: 'mdi-pencil', path: '/post' },
          { title: 'Login', icon: 'mdi-login', path: '/login' },
          { title: 'Register', icon: 'mdi-account-box-edit-outline', path: '/register' },
          { title: 'About', icon: 'mdi-information', path: '/about' },
          { title: 'Contact', icon: 'mdi-phone', path: '/contact' },
        ]
      }
    }
  },
  methods: {
    ...mapActions('auth', ['logout']),
    toggleDrawer() {
      this.drawer = !this.drawer;
    },
    doLogout() {
      this.logout();

      if(this.$route.name !== 'Home')
        router.push("/");   // home으로
    },
  },
};
</script>

 

computed의 GetMenuItems() 함수가 로그인 여부에 따라 다르게 탐색서랍과 툴바 메뉴명 항목 배열 반환합니다.

GetMenuItems() {
   if(this.user) {
     return [
       { title: 'Home', icon: 'mdi-home', path: '/' },
       { title: 'Post', icon: 'mdi-pencil', path: '/post' },
       { title: 'About', icon: 'mdi-information', path: '/about' },
       { title: 'Contact', icon: 'mdi-phone', path: '/contact' },
     ]
  } else {
     return [
       { title: 'Home', icon: 'mdi-home', path: '/' },
       { title: 'Post', icon: 'mdi-pencil', path: '/post' },
       { title: 'Login', icon: 'mdi-login', path: '/login' },
       { title: 'Register', icon: 'mdi-account-box-edit-outline', path: '/register' },
       { title: 'About', icon: 'mdi-information', path: '/about' },
       { title: 'Contact', icon: 'mdi-phone', path: '/contact' },
     ]
 }

 

Vue 프로젝트 Beta Test : mylog, 일상의 기록