Firebase Auth, Pinia Store 그리고 사용자 인증
프로젝트를 만들고, firebase를 설정하고, router와 pinia를 설치합니다.
Firebase Authentication의 주요 기능들을 통합한 Pinia store를 만들어
이를 이용하여 계정만들기와 로그인 페이지를 구현합니다.
1. 프로젝트 만들기
C:\2025\grand-fa_2025>npm create vite@latest my-auth -- --template vue
> npx
> cva my-auth --template vue
│
◇ Scaffolding project in C:\2025\grand-fa_2025\my-auth...
│
└ Done. Now run:
cd my-auth
npm install
npm run dev
C:\2025\grand-fa_2025>cd my-auth
C:\2025\grand-fa_2025\my-auth>npm install
added 30 packages, and audited 31 packages in 9s
4 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
C:\2025\grand-fa_2025\my-auth>
2. firebase
1. firebase 설치
npm install firebase
2. 환경 변수 - .env
3. firebase 설정
// src/firebase.js
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID,
};
// Firebase 초기화
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
export { auth, db };
3. Vue Router
Vue Router는 Vue 애플리케이션에서 페이지 간 전환을 관리하는 데 사용되며,
이를 통해 싱글 페이지 애플리케이션(SPA)에서 페이지 전환을 처리할 수 있습니다.
1. Vue Router 설치
먼저 Vue Router를 프로젝트에 설치해야 합니다.
npm install vue-router@4
2. 라우터 설정
Vue Router를 설정하려면 router/index.js 파일을 만들어서 라우터를 설정하고,
이를 main.js에 연결해야 합니다.
2.1 라우터 정의 (router/index.js)
라우터에서 경로(path)와 해당 경로에서 렌더링될 컴포넌트를 정의합니다.
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import Home from '@/views/Home.vue';
import Login from '@/views/Login.vue';
import Register from '@/views/Register.vue';
const routes = [
{ path: '/', name: 'Home', component: Home },
{ path: '/login', name: 'Login', component: Login },
{ path: '/register', name: 'Register', component: Register },
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
2.2. 라우터 사용 설정 (main.js)
main.js에서 Vue 애플리케이션에 라우터를 적용합니다.
// src/main.js
import { createApp } from 'vue';
import App from './App.vue'; // 루트 컴포넌트
import router from './router'; // 라우터 설정
// Vue 애플리케이션에 라우터를 사용하도록 설정
createApp(App)
.use(router) // 라우터 사용
.mount('#app'); // 앱을 #app 요소에 마운트
4. Pinia(상태 관리 라이브러리)
Pinia는 Vue 3의 공식 상태 관리 라이브러리로,
Vuex의 후속으로 설계되었습니다.
Vue 3의 Composition API와 잘 통합되며,
사용하기 간단하고 효율적입니다.
Pinia는 상태 관리에 필요한 기본적인 기능들을 제공하고,
Vue 3와 잘 맞도록 설계되었습니다.
4.1 Pinia 설치
Pinia를 사용하려면 먼저 프로젝트에 설치해야 합니다.
npm install pinia
4.2. Pinia 설정
Pinia를 사용하려면 main.js 또는 main.ts 파일에서 Pinia를 앱에 추가해야 합니다.
// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import { createPinia } from 'pinia'; // Pinia import
const app = createApp(App);
app.use(createPinia()); // Pinia 플러그인 사용
app.mount('#app');
5. Vuetify
🧩 Vuetify란?
Vuetify는 Vue.js를 위한 머티리얼 디자인 UI 프레임워크입니다.
"머티리얼 디자인 UI 프레임워크"는
구글의 머티리얼 디자인 가이드를 따르는 UI 컴포넌트들을 미리 만들어놓은 도구 모음이에요!
📦 설치 방법 (Vue 3 기준)
npm install vuetify@next
그리고 main.js 또는 main.ts에 아래처럼 등록:
// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import { createPinia } from 'pinia'; // Pinia import
const app = createApp(App);
app.use(createPinia()); // Pinia 플러그인 사용
app.mount('#app');
Vuetify에서 MDI(Material Design Icons) 아이콘 사용
MDI (Material Design Icons) 는 구글의 머티리얼 디자인 가이드라인을 따르는 아이콘 세트입니다. 이 아이콘들은 웹과 모바일 애플리케이션에서 자주 사용되는 아이콘들을 모아놓은 라이브러리로, SVG 형식으로 제공되며 쉽게 커스터마이징할 수 있습니다.
MDI는 구글의 머티리얼 디자인 철학을 기반으로 만들어져 있으며, 사용자 인터페이스(UI)에 적합한 다양한 아이콘을 제공합니다. 이 아이콘들은 웹 애플리케이션, 모바일 애플리케이션, 데스크탑 애플리케이션 등에서 일관성 있고 직관적인 디자인을 유지할 수 있도록 돕습니다.
✅ Vuetify 설정에서 아이콘 옵션 추가
🛠 설치 방식 확인
CDN이 아닌 NPM으로 mdi 폰트를 설치
npm install @mdi/font
그리고 main.js에서:
import '@mdi/font/css/materialdesignicons.css'
6. Vite에서 @ 경로 별칭이 설정되어 있는지 확인
Vite는 기본적으로 @를 src 폴더로 매핑하지 않으므로, vite.config.js에서 설정해야 합니다.
📌 vite.config.js 수정
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path"; // ✅ 경로 모듈 추가
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"@": path.resolve(__dirname, "src"), // ✅ "@/..." 사용 가능하게 설정
},
},
});
7. main.js
router, pinia, vuetify, MDI(Material Design Icons) 아이콘 사용 설정
// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import { createPinia } from 'pinia'; // Pinia import
import router from './router';
import 'vuetify/styles';
import { createVuetify } from 'vuetify';
import * as components from 'vuetify/components';
import * as directives from 'vuetify/directives';
import { aliases, mdi } from 'vuetify/iconsets/mdi' // 추가
import '@mdi/font/css/materialdesignicons.css'
const vuetify = createVuetify({
components,
directives,
icons: {
defaultSet: 'mdi', // 기본 아이콘 셋을 mdi로 설정
aliases,
sets: {
mdi,
},
},
});
const app = createApp(App);
app.use(createPinia()); // Pinia 플러그인 사용
app.use(router);
app.use(vuetify);
app.mount('#app');
8. Vuetify로 UI 만들기
<template>
<v-app>
<!-- App Bar -->
<v-app-bar app color="primary" dark>
<v-app-bar-nav-icon @click="drawer = !drawer" />
<v-toolbar-title>My Vuetify App</v-toolbar-title>
<v-spacer />
<div v-if="!auth.isAuthenticated">
<v-btn icon to="/login" title="로그인">
<v-icon>mdi-login</v-icon>
</v-btn>
</div>
<div v-else>
<v-btn icon @click="auth.logout" title="로그아웃">
<v-icon>mdi-logout</v-icon>
</v-btn>
</div>
</v-app-bar>
<!-- Navigation Drawer -->
<v-navigation-drawer app v-model="drawer" permanent>
<v-list nav dense>
<v-list-item
v-for="item in menuItems"
:key="item.title"
:to="item.to"
link
>
<v-list-item-title>
<v-icon start>{{ item.icon }}</v-icon>
{{ item.title }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-navigation-drawer>
<!-- Main Content -->
<v-main>
<v-container>
<router-view />
</v-container>
</v-main>
<!-- Footer -->
<v-footer app color="primary" dark>
<v-col class="text-center">© 2025 My App</v-col>
</v-footer>
</v-app>
</template>
<script setup>
import { ref } from 'vue';
import { useAuthStore } from '@/stores/authStore';
const auth = useAuthStore();
const drawer = ref(true);
// 메뉴 항목 배열
const menuItems = [
{ title: '홈', icon: 'mdi-home', to: '/' },
{ title: '로그인', icon: 'mdi-login', to: '/login' },
{ title: '계정 만들기', icon: 'mdi-account-plus', to: '/register' },
];
</script>
<style scoped>
.main-content {
display: flex;
flex-direction: column;
min-height: calc(100vh - 64px - 64px); /* AppBar와 Footer의 높이 */
}
</style>
9. Firebase Authentication의 주요 기능들을 통합한 Pinia store
// src/stores/authStore.js
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import { useRouter } from 'vue-router';
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
onAuthStateChanged,
sendPasswordResetEmail,
updatePassword,
GoogleAuthProvider,
signInWithPopup,
EmailAuthProvider,
reauthenticateWithCredential
} from 'firebase/auth';
import { auth } from '@/firebase'; // firebase.js에서 auth 객체 import
export const useAuthStore = defineStore('auth', () => {
const router = useRouter();
const user = ref(null);
const loading = ref(false);
const isAuthenticated = computed(() => !!user.value);
// 회원가입
const register = async (email, password) => {
try {
loading.value = true;
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
user.value = userCredential.user;
router.push('/');
} catch (error) {
alert('회원가입 실패: ' + error.message);
} finally {
loading.value = false;
}
};
// 로그인
const login = async (email, password) => {
try {
loading.value = true;
const userCredential = await signInWithEmailAndPassword(auth, email, password);
user.value = userCredential.user;
router.push('/');
} catch (error) {
alert('로그인 실패: ' + error.message);
} finally {
loading.value = false;
}
};
// 로그아웃
const logout = async () => {
try {
await signOut(auth);
user.value = null;
router.push('/');
} catch (error) {
alert('로그아웃 실패: ' + error.message);
}
};
// 로그인 상태 유지
const initializeAuth = () => {
onAuthStateChanged(auth, (currentUser) => {
user.value = currentUser;
});
};
// 비밀번호 재설정
const resetPassword = async (email) => {
try {
await sendPasswordResetEmail(auth, email);
alert('비밀번호 재설정 이메일을 보냈습니다.');
} catch (error) {
alert('재설정 실패: ' + error.message);
}
};
// 비밀번호 변경
const changePassword = async (currentPassword, newPassword) => {
try {
const credential = EmailAuthProvider.credential(auth.currentUser.email, currentPassword);
await reauthenticateWithCredential(auth.currentUser, credential);
await updatePassword(auth.currentUser, newPassword);
alert('비밀번호가 변경되었습니다.');
} catch (error) {
alert('비밀번호 변경 실패: ' + error.message);
}
};
// 구글 로그인
const loginWithGoogle = async () => {
try {
const provider = new GoogleAuthProvider();
const result = await signInWithPopup(auth, provider);
user.value = result.user;
router.push('/');
} catch (error) {
alert('구글 로그인 실패: ' + error.message);
}
};
return {
user,
isAuthenticated,
loading,
register,
login,
logout,
resetPassword,
changePassword,
loginWithGoogle,
initializeAuth,
};
});
10. 이메일, 패스워드로 계정 만들기, 그리고 로그인
1. Home
<template>
<h1>홈 페이지</h1>
</template>
<script setup>
</script>
2. 계정 만들기
<!-- src/views/Register.vue -->
<template>
<v-container fluid>
<v-row align="center" justify="center">
<v-col cols="8">
<v-card elevation="10" class="pa-4">
<v-card-title class="text-h6 font-weight-bold">계정 만들기</v-card-title>
<v-card-text>
<v-form @submit.prevent="onRegister" ref="formRef" v-model="formValid">
<v-text-field
v-model="email"
label="이메일"
type="email"
:rules="emailRules"
prepend-inner-icon="mdi-email"
required
/>
<v-text-field
v-model="password"
label="비밀번호"
type="password"
:rules="passwordRules"
prepend-inner-icon="mdi-lock"
required
/>
<v-btn :loading="auth.loading" type="submit" color="primary" class="mt-4" block>
회원가입
</v-btn>
</v-form>
</v-card-text>
<v-card-actions class="justify-center">
<RouterLink to="/login">이미 계정이 있으신가요?</RouterLink>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-container>
</template>
<script setup>
import { ref } from 'vue';
import { useAuthStore } from '@/stores/authStore';
const auth = useAuthStore();
const email = ref('');
const password = ref('');
const formValid = ref(false);
const formRef = ref(null);
const emailRules = [
(v) => !!v || '이메일은 필수입니다.',
(v) => /.+@.+\..+/.test(v) || '올바른 이메일 형식이 아닙니다.',
];
const passwordRules = [
(v) => !!v || '비밀번호는 필수입니다.',
(v) => v.length >= 6 || '비밀번호는 최소 6자리 이상이어야 합니다.',
];
const onRegister = () => {
if (formRef.value?.validate()) {
auth.register(email.value, password.value);
}
};
</script>
<style scoped>
.fill-height {
min-height: 100vh;
}
</style>
3. Login
<!-- src/views/Login.vue -->
<template>
<v-container fluid>
<v-row align="center" justify="center">
<v-col cols="8">
<v-card elevation="10" class="pa-4">
<v-card-title class="text-h6 font-weight-bold">로그인</v-card-title>
<v-card-text>
<v-form @submit.prevent="onLogin" ref="formRef" v-model="formValid">
<v-text-field
v-model="email"
label="이메일"
type="email"
:rules="emailRules"
prepend-inner-icon="mdi-email"
required
/>
<v-text-field
v-model="password"
label="비밀번호"
type="password"
:rules="passwordRules"
prepend-inner-icon="mdi-lock"
required
/>
<v-btn :loading="auth.loading" type="submit" color="primary" class="mt-4" block>
로그인
</v-btn>
<v-btn variant="outlined" block class="mt-2" @click="auth.googleLogin">
<v-icon start>mdi-google</v-icon>
구글로 로그인
</v-btn>
</v-form>
</v-card-text>
<v-card-actions class="justify-center">
<RouterLink to="/register">계정이 없으신가요?</RouterLink>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-container>
</template>
<script setup>
import { ref } from 'vue';
import { useAuthStore } from '@/stores/authStore';
const auth = useAuthStore();
const email = ref('');
const password = ref('');
const formValid = ref(false);
const formRef = ref(null);
const emailRules = [
(v) => !!v || '이메일은 필수입니다.',
(v) => /.+@.+\..+/.test(v) || '올바른 이메일 형식이 아닙니다.',
];
const passwordRules = [
(v) => !!v || '비밀번호는 필수입니다.',
(v) => v.length >= 6 || '비밀번호는 최소 6자리 이상이어야 합니다.',
];
const onLogin = () => {
if (formRef.value?.validate()) {
auth.login(email.value, password.value);
}
};
</script>
<style scoped>
.fill-height {
min-height: 100vh;
}
</style>
🎉 이제 실행해봅시다.
npm run dev
'Vue3, Firebase 프로젝트 - 채팅앱 VSignal' 카테고리의 다른 글
28. 사용자 로그인 상태 감지하여 프로필 정보 가져오기 (1) | 2025.05.03 |
---|---|
27. Vue 3 + Vuetify + Pinia + Firebase Firestore 기반의 계정 설정 (0) | 2025.05.03 |
25. 이메일, 패스워드로 계정 만들기, 그리고 로그인 (0) | 2025.05.01 |
24. Firebase Authentication의 주요 기능들을 통합한 Pinia store (0) | 2025.04.30 |
23. 라우터 설정 (0) | 2025.04.28 |