토이 프로젝트 - Vue, Firebase로 서버리스 PWA 개발

7. Vue(with Vuetify)와 Firebase로 서버리스 PWA myBlog 개발 - createUserWithEmailAndPassword

그랜파 개발자 2025. 2. 6. 05:40

사용자 인증

Firebase Authentication - createUserWithEmailAndPassword

계정 만들기는 Firebase Authentication에서 제공하는 createUserWithEmailAndPassword를 사용합니다.
createUserWithEmailAndPassword는 Firebase Authentication에서 제공하는 메서드로,
이메일과 비밀번호를 기반으로 Firebase 프로젝트의 인증 시스템에 새로운 사용자 계정을 생성합니다.

이 메서드를 호출하면 Firebase는 제공된 이메일과 비밀번호로 인증된 새 계정을 생성하고,
이 데이터는 Firebase가 자체적으로 제공하는 Firebase Authentication 전용 데이터베이스에 저장하고,
Firebase Authentication에서 관리되며, 사용자가 직접 관리할 필요는 없습니다.

 

Firebase Authentication은 사용자 계정 생성만 처리합니다.
기본 정보 외에 사용자 관련 추가 데이터를 저장하려면 Firestore와 함께 사용해야 합니다.
사용자 관련 추가 데이터는 계정 생성을 하면서 계정 설정 정보로 함께 생성하여,
Firestore의 profiles 컬렉션에 저장합니다.

 

createUserWithEmailAndPassword 설명

createUserWithEmailAndPassword는 Firebase Authentication에서 제공하는 메서드로, 사용자의 이메일과 비밀번호를 기반으로 새로운 계정을 생성합니다.
이 메서드는 클라이언트 애플리케이션에서 Firebase의 인증 시스템과 상호작용하기 위해 사용됩니다.

메서드 정의

createUserWithEmailAndPassword(auth, email, password)

매개변수

  • auth:
    Firebase Authentication 인스턴스입니다. (getAuth()로 가져올 수 있음)
  • email:
    사용자가 가입하려는 이메일 주소.
  • password:
    사용자가 설정한 비밀번호. 최소 6자 이상이어야 합니다.

반환값

Promise<UserCredential>: 성공하면 사용자 계정이 생성되고, UserCredential 객체가 반환됩니다.

  • user: 생성된 사용자의 정보 (uid, email 등 포함).
  • accessToken: 인증에 사용할 토큰.

성공 시 동작

Firebase는 사용자의 이메일과 비밀번호를 기반으로 새로운 계정을 생성하고, 이 계정을 Firebase Authentication에 저장합니다.
사용자 정보는 Firebase 콘솔의 Authentication 섹션에서 확인할 수 있습니다.
기본적으로 로그인된 상태로 전환됩니다.

실패 시 처리

createUserWithEmailAndPassword 메서드는 실패 시 FirebaseError 객체를 반환하며, code와 message를 통해 오류를 확인할 수 있습니다.

자주 발생하는 에러 코드
- auth/email-already-in-use:
이미 존재하는 이메일 주소로 계정을 생성하려 할 때 발생.
- auth/invalid-email:
잘못된 이메일 형식.
- auth/weak-password:
비밀번호가 6자 미만일 때 발생.

Firebase Authentication에 저장되는 정보

사용자가 createUserWithEmailAndPassword로 계정을 생성하면, Firebase는 다음 정보를 저장합니다:

  • 이메일 (email): 사용자 이메일 주소.
  • UID (user ID): 고유한 사용자 ID.
  • 계정 생성 날짜.
  • 비밀번호: 암호화된 형태로 저장 (Firebase는 암호화를 처리해 주므로 직접 암호화를 구현할 필요 없음).

계정 설정 항목

Firebase Authentication은 사용자 계정 생성만 처리합니다.
우리는 이메일과 비밀번호로 계정을 생성하였습니다.
비밀번호는 Firebase Authentication에 저장하므로 계정 설정 항목은 아닙니다.

계정 설정을 통한 추가 회원 정보는 firesore의 profiles 컬렉션에 저장합니다.
이메일과 계정 생성에서 돌려받은 User 객체의 uid를 userId로 하는 계정 설정 항목은 다음과 같습니다.

  • userId : 로그인 Id입니다. 계정 만들기에 등록된 정보입니다.
  • email : 로그인할 때 이메일입니다. 이미 계정 생성에서 등록된 정보이므로 계정 설정에서는 입력하지 않습니다.
  • name : 사용자의 이름 또는 별명입니다.
  • blogName : 사용자의 블로그 이름입니다.
  • aboutMe : 사용자의 현재 상태 정보입니다.
  • createdAt : 계정 정보 생성일입니다. 계정 설정 정보를 저장할 때 시스템 시간을 자동으로 얻어 저장합니다.
  • uids[] : 여러 OAuth를 이용한 로그인을 위한 것입니다.

계정 설정 정보 중에 생성을 할 때 정해지는 정보는 userId, email, createAt, uids 입니다.
name, blogName, aboutMe는 계정 생성 상황에서 알 수 없으므로
공란으로 계정을 생성할 때 계정 설정 정보를 저장할 수 있습니다.

계정 생성을 하면서 계정 설정 정보를 profiles 컬렉션에 저장하고,
계정 설정 페이지에서 name, blogName, aboutMe 정보를 입력하도록 합니다.

계정 생성을 하면 로그인 상태가 되므로, 계정 정보를 저장한 후 다시 상태 변수에 로드합니다.

myBlog 계정 만들기

store module - auth

// src/store/modules/auth.js
import { auth, db, collection, doc, getDocs, addDoc, updateDoc,
  query, where } from "@/firebase";
import { createUserWithEmailAndPassword, signInWithEmailAndPassword, 
  signOut, sendPasswordResetEmail, onAuthStateChanged } from "firebase/auth";

const state = {
  user: null,     // 현재 로그인한 회원
  isNewProfile: false,  // 계정 설정이 새로운 계정인가 기존에 등록된 계정인가 여부
  profile: null,  // 회원 정보
  profiles: [],   // 모든 회원의 계정 설정 정보
};

const mutations = {
  setUser(state, user) {
    state.user = user;
  },
  setProfile(state, profile) {
    state.profile = profile;
  },
  setProfiles(state, profiles) {
    state.profiles = profiles;
  },
  setIsNewProfile(state, isNewProfile) {
    state.isNewProfile = isNewProfile;
  },
};

const actions = {
  // -- 계정 만들기
  async register({ commit, dispatch }, { email, password }) {
    try {
      const userCredential = await createUserWithEmailAndPassword(auth, email, password);
      // 로그인 설정
      commit("setUser", userCredential.user);    

      // 계정 설정 정보 저장 - 공란으로 저장하고, 계정 설정에서 정보를 입력한다.
      const profile = {
        userId: userCredential.user.uid,
        email:email,
        name: '',   
        blogName: '',   
        aboutMe: '',    
        createdAt: new Date(),  
        uids: [userCredential.user.uid]
      }

      dispatch('registerProfile', profile);

    } catch (error) {
      alert("register : " + error.message);
      commit("setUser", null); 
    }
  }, 

  // -- 앱을 시작하면 자동 로그인을 설정한다.
  async initializeAuth({ commit, dispatch }) {
    onAuthStateChanged(auth, (user) => {
      if (user) {        
        commit("setUser", user);  // 로그인 사용자를 상태 변수에 설정한다.
        dispatch('fetchProfile', user); // profile 정보를 로드한다.
      } else {
        commit("setUser", null);
      }
    });
  },

  // -- 계정 설정 정보 로드
  async fetchProfile({ commit }, user) {
    try {
      // Firestore에서 'uids' 배열에 UID가 포함된 문서 검색
      const q = query(
        collection(db, "profiles"),
        where("uids", "array-contains", user.uid)
      );
      const querySnapshot = await getDocs(q);

      // 결과를 users 배열에 저장
      let profiles = [];
      profiles = querySnapshot.docs.map((doc) => ({
        id: doc.id,
        ...doc.data(),
      }));

      // 계정은 생성되었지만 '계정 설정' 정보가 없는 경우
      if (profiles.length === 0) {
        const profile = {
          userId: user.uid,
          email:user.email,
          name: '', 
          blogName: '', 
          aboutMe: '',  
          createdAt: new Date(),    
          uids: [user.uid]
        }
        //console.log(profile);
        commit("setProfile",profile);
        commit("setIsNewProfile",true); // 새로운 계정 설정 정보
      } else {
        // 이밎 저장된 profile이 있는 경우
        commit("setProfile", profiles[0]);
        commit("setIsNewProfile", false); // 이미 등록된 회원 정보
      }
    } catch (error) {
      alert("Failed to fetch users. : " + error.message);
    }
  },

  // 새 계정 설정 정보를 저장한다.
  async registerProfile({ commit, dispatch, state }, profile) {
    try {
      const docRef = await addDoc(collection(db, "profiles"), { profile });
       // 계정 설정 정보를 저장한 후 상태 변수에 로드한다.
      dispatch('fetchProfile', state.user);
    } catch (error) {
      alert("Failed to register user. : " + error.message);
    }
  },
};

const getters = { 

};

export default {
  namespaced: true,
  state,
  mutations,
  actions,
  getters
};

RegisterView.vue

<!-- src/views/RegisterView.vue -->
<template>
  <v-card>
    <v-card-title>계정 만들기</v-card-title>
    <v-card-text>
      <v-form @submit.prevent="doRegister">
        <v-text-field v-model="email" label="Email" type="email" required></v-text-field>
        <v-text-field v-model="password" label="Password" type="password" required></v-text-field>
        <v-btn type="submit" color="primary">계정 만들기</v-btn>
      </v-form>
    </v-card-text>
  </v-card>
</template>

<script>
import router from '@/router';  // Vue Router import
import { mapActions } from "vuex";

export default {
  data() {
    return {
      email: '',
      password: '',
    };
  },

  methods: {
    ...mapActions('auth', ['register']),

    async doRegister() {
      await this.register({email:this.email, password: this.password});
      router.push("/");   // home으로
    },
  },
};
</script>