117. Logging the user in after Signup
npm create vue@latest
이번엔 사용자가 등록한 후 로그인하는 과정을 다룹니다.
사용자 등록은 RegisterForm
컴포넌트에서 처리되며, createUserWithEmailAndPassword
함수를 사용하여 사용자를 생성하고 인증합니다.
계정 생성이 성공하면, 함수의 응답으로 사용자의 자격 증명이 반환됩니다.
이는 userCredentials
라는 변수에 저장됩니다.Firebase SDK
를 사용함으로써, 토큰은 자동으로 저장되고 필요한 서비스에 전송됩니다.
예를 들어 Firestore가 이에 해당합니다.
Firebase의 보안 규칙을 변경하여, 인증된 사용자만 데이터베이스에 쓸 수 있도록 했습니다.
따라서 Firebase SDK
는 요청과 함께 토큰을 전송합니다.
Firebase 콘솔을 사용하여 사용자를 삭제하고 데이터베이스의 문서를 삭제한 후, 앱에서 새 계정을 등록하여 테스트할 수 있습니다.
이 과정에서 중요한 점은 사용자를 인증 서비스에 등록한 후 데이터베이스에 추가하는 순서입니다.
이 순서를 바꾸면 Firebase가 오류를 발생시킬 것입니다.
추가로 register
함수에 수정을 가하여 사용자가 로그인했는지 추적하기 위해 상태(state
)에 불린 값(boolean
)을 저장합니다.
Pinia 패키지의 defineStore
함수를 사용하여 새로운 user.ts
스토어 파일을 생성합니다.
이 스토어에서는 사용자가 로그인했는지 여부를 나타내는 user logged in
상태를 false
로 초기화합니다.
사용자가 성공적으로 등록된 후 이상태를 true
로 업데이트하며, 이를 통해 앱에서 사용자의 인증 상태를 추적할 수 있습니다.
Firebase 콘솔에서 사용자를 삭제하고 앱에서 등록 양식을 다시 제출하여 테스트할 수 있습니다.
import { defineStore } from 'pinia'
import { ref } from 'vue'
const useUser = defineStore('user', () => {
const userLoggedIn = ref(false)
return {
userLoggedIn
}
})
export default useUser
<script setup lang="ts">
import { ref } from 'vue'
import { createUserWithEmailAndPassword } from 'firebase/auth'
import { auth, usersCollection } from '@/includes/firebase'
import { addDoc } from 'firebase/firestore'
import useUser from '@/stores/user'
import { storeToRefs } from 'pinia'
const userStore = useUser()
const { userLoggedIn } = storeToRefs(userStore)
const schema = ref({
name: 'required|min:3|max:100|alpha_spaces',
email: 'required|min:3|max:100|email',
age: 'required|min_value:18|max_value:100',
password: 'required|min:9|max:100|excluded:password', // password 단어를 입력하면 유효성 검사를 통과할 수 없다.
confirm_password: 'passwords_mismatch:@password', // 비밀번호 입력할 때, 비밀번호 재확인차 입력하는 필드 // 위의 password 부분과 일치하는지 확인함
country: 'required|country_excluded:Antarctica',
tos: 'required'
})
const userData = ref({
country: 'USA'
})
const reg_in_submission = ref(false)
const reg_show_alert = ref(false)
const reg_alert_variant = ref('bg-blue-500')
const reg_alert_msg = ref('Please wait! Your acount is being created.')
const register = async (values: {
age: number
confirm_password: string
country: string
email: string
name: string
password: string
tos: number
}) => {
// VeeForm 안에 VeeField의 모든 유효성 검사를 통과해야지 values 값이 들어온다.
// ex.
// {
// age: 31
// confirm_password: "asdf"
// country: "Germany"
// email: "sdf@sdf.com"
// name: "sfdf"
// password: "asdf"
// tos: "1"
// }
reg_show_alert.value = true
reg_in_submission.value = true
reg_alert_variant.value = 'bg-blue-500'
reg_alert_msg.value = 'Please wait! Your account is being created.'
let userCred
try {
userCred = await createUserWithEmailAndPassword(auth, values.email, values.password)
} catch (error) {
reg_in_submission.value = false
reg_alert_variant.value = 'bg-red-500'
reg_alert_msg.value = 'An unexpected error occured. Please try again later.'
return
}
try {
await addDoc(usersCollection, {
name: values.name,
email: values.email,
age: values.age,
country: values.country
})
} catch (error) {
reg_in_submission.value = false
reg_alert_variant.value = 'bg-red-500'
reg_alert_msg.value = 'An unexpected error occured. Please try again later.'
return
}
// Firebase는 회원 등록이 끝나면 자동으로 로그인처리해줌
// 따라서 그에맞게 로그인 여부 상태값 true로 갱신
userLoggedIn.value = true
reg_alert_variant.value = 'bg-green-500'
reg_alert_msg.value = 'Success! Your account has been created.'
console.log(userCred)
}
</script>
<template>
<!-- Registration Form -->
<div
class="text-white text-center font-bold p-4 rounded mb-4"
v-if="reg_show_alert"
:class="reg_alert_variant"
>
{{ reg_alert_msg }}
</div>
<VeeForm :validation-schema="schema" @submit="register" :initial-values="userData">
<!-- Name -->
<div class="mb-3">
<label class="inline-block mb-2">Name</label>
<VeeField
type="text"
name="name"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
placeholder="Enter Name"
/>
<ErrorMessage class="text-red-600" name="name" />
</div>
<!-- Email -->
<div class="mb-3">
<label class="inline-block mb-2">Email</label>
<VeeField
type="email"
name="email"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
placeholder="Enter Email"
/>
<ErrorMessage class="text-red-600" name="email" />
</div>
<!-- Age -->
<div class="mb-3">
<label class="inline-block mb-2">Age</label>
<VeeField
type="number"
name="age"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
/>
<ErrorMessage class="text-red-600" name="age" />
</div>
<!-- Password -->
<div class="mb-3">
<label class="inline-block mb-2">Password</label>
<VeeField name="password" :bails="false" #default="{ field, errors }">
<input
type="password"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
placeholder="password"
v-bind="field"
/>
<div class="text-red-600" v-for="error in errors" :key="error">
{{ error }}
</div>
</VeeField>
</div>
<!-- Confirm Password -->
<div class="mb-3">
<label class="inline-block mb-2">Confirm Password</label>
<VeeField
type="password"
name="confirm_password"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
placeholder="Confirm Password"
/>
<ErrorMessage class="text-red-600" name="confirm_password" />
</div>
<!-- Country -->
<div class="mb-3">
<label class="inline-block mb-2">Country</label>
<VeeField
as="select"
name="country"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
>
<option value="USA">USA</option>
<option value="Mexico">Mexico</option>
<option value="Germany">Germany</option>
<option value="Antarctica">Antarctica</option>
</VeeField>
<ErrorMessage class="text-red-600" name="country" />
</div>
<!-- TOS -->
<div class="mb-3 pl-6">
<VeeField
type="checkbox"
name="tos"
value="1"
class="w-4 h-4 float-left -ml-6 mt-1 rounded"
/>
<label class="inline-block">Accept terms of service</label>
<ErrorMessage class="text-red-600 block" name="tos" />
</div>
<button
type="submit"
class="block w-full bg-purple-600 text-white py-1.5 px-3 rounded transition hover:bg-purple-700"
:disabled="reg_in_submission"
>
Submit
</button>
</VeeForm>
</template>
<style scoped></style>