Firebase Hosting으로 실제 웹 서비스 오픈까지 진행합니다.
서비스 오픈까지 개발을 진행하면서 계속 수정, 변경될 것입니다.
비밀번호 변경, 비밀번호 재설정
보안상의 이유로 비밀번호는 주기적으로 변경하는 것이 좋습니다.
비밀번호 변경은 로그인 후에 가능하므로 프로필 페이지 기능을 둡니다.
비밀번호 리셋 기능을 비밀번호를 잊었을 때 사용합니다.
비밀번호 리셋 기능을 로그인 페이지에 기능을 둡니다.
비밀번호 변경
보안상의 이유로 비밀번호는 주기적으로 변경하는 것이 좋습니다.
비밀번호 변경은 로그인 후에 가능하므로 프로필 페이지 기능을 둡니다.
updatePassword()는 Firebase Authentication에서 현재 로그인된 사용자의 비밀번호를 새 비밀번호로 변경할 때 사용하는 메서드입니다.
비밀번호를 변경하는 것은 민감한 작업이므로 사용자의 신원을 다시 확인한 후 비밀번호를 변경합니다.
reauthenticateWithCredential은 Firebase Authentication에서 이미 로그인한 사용자가 자신의 인증 상태를 다시 확인(재인증) 해야 할 때 사용하는 메서드입니다.
✅ 예시: 이메일/비밀번호로 재인증
import { reauthenticateWithCredential, EmailAuthProvider } from 'firebase/auth'
import { auth } from './firebase'
// 사용자가 입력한 현재 비밀번호를 받아 재인증
const reauthenticate = async (currentPassword) => {
const user = auth.currentUser
if (!user) throw new Error('로그인된 사용자가 없습니다.')
const credential = EmailAuthProvider.credential(user.email, currentPassword)
try {
await reauthenticateWithCredential(user, credential)
console.log('재인증 성공')
} catch (error) {
console.error('재인증 실패:', error.message)
}
}
비밀번호 리셋
비밀번호 리셋은 비밀번호를 잊었을 때 사용하는 기능입니다.
그러므로 비밀번호 재설정 기능을 로그인 창에 둡니다.
메일 주소를 입력하고 ‘비밀번호를 잊으셨나요?’를 클릭하면 비밀번호 재설정 메일이 전송됩니다.
sendPasswordResetEmail()은 Firebase Authentication에서 사용자가 비밀번호를 재설정할 수 있는 이메일을 전송할 때 사용하는 메서드입니다.
src/stores/authStore.js
// src/stores/authStore.js
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { auth, db } from '../firebase'
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
onAuthStateChanged,
updatePassword,
sendPasswordResetEmail,
EmailAuthProvider,
reauthenticateWithCredential
} from 'firebase/auth'
import { doc, setDoc, getDoc } from 'firebase/firestore'
export const useAuthStore = defineStore('auth', () => {
const user = ref(null)
const profile = ref(null)
const register = async (email, password, name, aboutMe) => {
const userCredential = await createUserWithEmailAndPassword(auth, email, password)
user.value = userCredential.user
const profileData = {
uid: user.value.uid,
name,
aboutMe,
email: user.value.email,
createdAt: new Date(),
}
await setDoc(doc(db, 'profiles', user.value.uid), profileData)
profile.value = profileData
}
const login = async (email, password) => {
const userCredential = await signInWithEmailAndPassword(auth, email, password)
user.value = userCredential.user
const profileDoc = await getDoc(doc(db, 'profiles', user.value.uid))
profile.value = profileDoc.exists() ? profileDoc.data() : null
}
const logout = async () => {
await signOut(auth)
user.value = null
profile.value = null
}
const initAuth = () => {
onAuthStateChanged(auth, async (currentUser) => {
user.value = currentUser
if (currentUser) {
const profileDoc = await getDoc(doc(db, 'profiles', currentUser.uid))
profile.value = profileDoc.exists() ? profileDoc.data() : null
} else {
profile.value = null
}
})
}
const changePassword = async (currentPassword, newPassword) => {
const user = auth.currentUser
if (!user || !user.email) {
throw new Error('사용자 정보가 없습니다.')
}
const credential = EmailAuthProvider.credential(user.email, currentPassword)
await reauthenticateWithCredential(user, credential)
await updatePassword(user, newPassword)
}
const resetPassword = async (email) => {
await sendPasswordResetEmail(auth, email)
}
return {
user,
profile,
register,
login,
logout,
initAuth,
changePassword,
resetPassword,
}
})
src/views/Profile.vue
<!-- src/views/Profile.vue -->
<template>
<v-container class="d-flex justify-center align-start" style="min-height: 100vh;">
<v-card width="400" class="pa-4">
<h2 class="text-h6 mb-4">프로필</h2>
<v-form @submit.prevent="save">
<v-text-field v-model="email" label="이메일" readonly />
<v-text-field v-model="name" label="Name" />
<v-textarea v-model="aboutMe" label="About Me" rows="3" />
<v-btn type="submit" color="primary" class="mb-6">Save</v-btn>
</v-form>
<h3 class="text-subtitle-1 mb-2">비밀번호 변경</h3>
<v-form @submit.prevent="changePassword">
<v-text-field
v-model="currentPassword"
label="현재 비밀번호"
type="password"
required
/>
<v-text-field
v-model="newPassword"
label="새 비밀번호"
type="password"
required
/>
<v-text-field
v-model="confirmPassword"
label="비밀번호 확인"
type="password"
:error="confirmPassword && confirmPassword !== newPassword"
:error-messages="confirmPassword && confirmPassword !== newPassword ? '비밀번호가 일치하지 않습니다.' : ''"
required
/>
<v-btn
type="submit"
color="secondary"
:disabled="newPassword !== confirmPassword"
>
비밀번호 변경
</v-btn>
</v-form>
</v-card>
</v-container>
</template>
<script setup>
import { ref, watchEffect } from 'vue'
import { useAuthStore } from '../stores/authStore'
import { doc, updateDoc } from 'firebase/firestore'
import { db } from '../firebase'
const authStore = useAuthStore()
const email = ref('')
const name = ref('')
const aboutMe = ref('')
const currentPassword = ref('') // 추가
const newPassword = ref('')
const confirmPassword = ref('')
watchEffect(() => {
if (authStore.profile) {
email.value = authStore.profile.email
name.value = authStore.profile.name
aboutMe.value = authStore.profile.aboutMe
}
})
const save = async () => {
const docRef = doc(db, 'profiles', authStore.user.uid)
await updateDoc(docRef, {
name: name.value,
aboutMe: aboutMe.value,
})
// 업데이트된 프로필을 다시 읽어와서 authStore.profile에 반영
authStore.profile = {
...authStore.profile,
name: name.value,
aboutMe: aboutMe.value,
}
alert('프로필이 저장되었습니다.')
}
const changePassword = async () => {
if (!newPassword.value || newPassword.value !== confirmPassword.value) {
alert('비밀번호가 올바르지 않거나 일치하지 않습니다.')
return
}
try {
// authStore의 changePassword 함수에 재인증 포함됨
await authStore.changePassword(currentPassword.value, newPassword.value)
alert('비밀번호가 변경되었습니다.')
// 입력 초기화
currentPassword.value = ''
newPassword.value = ''
confirmPassword.value = ''
} catch (error) {
alert('비밀번호 변경 실패: ' + error.message)
}
}
</script>
src/views/Login.vue
<!-- src/views/Login.vue -->
<template>
<v-container class="d-flex justify-center align-start" style="min-height: 100vh;">
<v-card width="400" class="pa-4">
<v-card-title class="text-h6">로그인</v-card-title>
<v-form @submit.prevent="login">
<v-text-field
v-model="email"
label="이메일"
type="email"
required
prepend-inner-icon="mdi-email"
/>
<v-text-field
v-model="password"
label="비밀번호"
type="password"
required
prepend-inner-icon="mdi-lock"
/>
<v-btn type="submit" color="primary" block class="mt-4">로그인</v-btn>
</v-form>
<v-btn
variant="text"
class="mt-2"
@click="resetPassword"
block
>
비밀번호를 잊으셨나요?
</v-btn>
</v-card>
</v-container>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/authStore'
const router = useRouter()
const authStore = useAuthStore()
const email = ref('')
const password = ref('')
const login = async () => {
try {
await authStore.login(email.value, password.value)
router.push('/')
} catch (error) {
alert(error.message || '로그인에 실패했습니다.')
}
}
const resetPassword = async () => {
if (!email.value) {
alert('비밀번호 재설정을 위해 이메일을 입력해주세요.')
return
}
try {
await authStore.resetPassword(email.value)
alert('비밀번호 재설정 이메일이 전송되었습니다.')
} catch (error) {
alert(error.message || '비밀번호 재설정에 실패했습니다.')
}
}
</script>
'예약 포털 (Vue3 + Firebase) - 서비스 오픈까지' 카테고리의 다른 글
6. 동네 (예약) 포털 (Vue 3 + Firebase) - 회원의 업체 관리 (0) | 2025.06.01 |
---|---|
5. 동네 (예약) 포털 (Vue 3 + Firebase) - 구글 계정으로 로그인 (0) | 2025.05.30 |
3. 동네 (예약) 포털 (Vue 3 + Firebase) - 회원 등록, 로그인 (0) | 2025.05.29 |
2. 동네 (예약) 포털 - Vue 3 + Vite + Vuetify 3 + JS 프로젝트 (0) | 2025.05.29 |
1. 내 주변 포털 - Vue3 + Vuetify 3 + Firebase (1) | 2025.05.29 |