마이 블로그 프로젝트 02 - 회원가입.

이유승·2023년 7월 16일
0

오직 나만 글을 작성하는 개발 블로그이고, 댓글 작성은 회원 여부와 무관하게 작성이 가능하도록 할 생각이다. 따라서 회원가입은 이 프로젝트에서 별로 중요한 기능은 아니지만 앞으로 여러 프로젝트를 진행하면서 회원 기능을 구현 할 일이 많기 때문에 연습의 일환으로 회원가입 기능 구현을 시작하였다.



1. UI와 기본 기능.

return (
    <div className={styles.signup}>
        <form className={styles.signupform} onSubmit={loginEvent}>
            <p>회원가입</p>
            <div className={styles.input}>
                <label htmlFor='id'>email : </label>
                <input name='id' type='text' required placeholder='이메일을 입력해주세요' value={userData.id} onChange={onChangeEvent}/>
            </div>
            <div className={styles.input}>
                <label htmlFor='pwd'>password : </label>
                <input name='pwd' type='password' required placeholder='비밀번호를 입력해주세요' value={userData.pwd} onChange={onChangeEvent}/>
            </div>
            <div className={styles.input}>
                <label htmlFor='nickname'>nickname : </label>
                <input name='nickname' type='text' required placeholder='닉네임을 입력해주세요' value={userData.nickname} onChange={onChangeEvent}/>
            </div>
            {!isPending && <button type='submit' className={styles.submitbutton}>입력</button>}
            {isPending && <><br/><strong>회원가입이 진행중입니다...</strong></>}
            {error && <><br/><strong>회원가입 에러가 발생하였습니다...</strong></>}
        </form>
    </div>
);

회원가입을 위해서는 사용자가 자신의 아이디와 비밀번호를 입력하고, 이를 받아 백엔드단으로 넘겨주어야 한다. form 태그로 UI의 기본을 만들고 input 태그와 submit 타입의 버튼 태그까지 만들어 준다.

다음은 값을 받아서 백엔드로 넘겨줄 기능을 구현해야 한다.

	const { error, isPending, signup } = useSignup();

    const [userData, setUserData] = useState({
        id: '',
        pwd: '',
        nickname: ''
    });

    const loginEvent = (event) => {
    	// 새로고침 방지
        event.preventDefault();
        signup(userData.id, userData.pwd, userData.nickname);
    };    

    const onChangeEvent = (event) => {
        setUserData({
            ...userData,
            [event.target.name] : event.target.value
        })
    };

useState로 필요한 데이터를 관리할 수 있도록 해주고, input 태그의 onChangeEvent 함수를 구현하여 사용자가 값을 입력할 때마다 setState가 일어나 값을 갱신해주도록 만든다. 하나의 state에 모든 값을 관리하기 위해 입력값을 객체 데이터로 관리하였고, 스프레드 연산자로 리액트의 불변성을 지키며 input 태그의 name 속성을 이용하여 사용자가 수정한 값만이 갱신되도록 구현하였다.

필요한 모든 데이터를 입력하였다면, 다음에는 이 값들을 백엔드로 넘겨주면 된다. form 태그의 submit 타입 버튼은 HTML 특성상 새로고침을 발생시킨다. 리액트에서 새로고침이 일어나면 컴포넌트가 리렌더링되면서 모든 state가 초기화되고, 당연히 백엔드로 제대로 된 값이 넘어가지지 않게 된다. 따라서 event 객체의 preventDefault() 함수를 호출하여 submit 버튼의 기본 이벤트(새로고침)이 일어나지 못하게 막아준다.

그리고 userData에 저장된 값을 내가 구현한 useSignup 함수로 전달해준다.

코드 평가.

평가 방법, 개인적인 코드 리뷰 및 Chat GPT 사용.

-> 보안 취약. input 태그의 password 타입 적용만으로는 외부 공격으로부터 완전한 보호가 어려움. 보안 강화 방안을 구상할 것!
-> 에러 처리 보강 필요. 현재 회원 기능 UI의 에러 처리는 문제가 발생했을 때, 에러가 발생했다는 메시지만 띄워주고 있음. 발생한 에러에 대한 상세 정보를 다룰 곳이 필요함. (백엔드에서는 error 변수에 에러 메시지가 저장되도록 하고있으나 파이어베이스에서 반환하는 에러 메시지가 영문이었기에 사용자에게 불편함을 줄 수도 있다고 판단하여 에러 발생 여부만 체크하도록 하였다.)



2. 회원 가입 기능.

회원 가입 기능은 useSignup Hook을 직접 만들어 구현하였다. 다른 곳에서 사용되거나 하지 않기 때문에 재사용성을 위해 굳이 별도로 분리할 필요성은 낮지만, 코드 모듈화에 대한 경험을 쌓을 겸 그리고 코드의 유지보수를 간편하게 하기 위해서 따로 분리하여 구현하였다.

import { useState } from 'react'
import { appAuth } from '../configs/firebase'
import { createUserWithEmailAndPassword, updateProfile } from 'firebase/auth'
import { useAuthContext } from './useAuthContext';
import { useNavigate } from 'react-router-dom';

export const useSignup = () => {
    // 상태 변수 설정
    const [error, setError] = useState(null);
    const [isPending, setIsPending] = useState(false);

  // useAuthContext 훅으로부터 dispatch 함수 받아오기
  const { dispatch } = useAuthContext();

  // React Router의 useNavigate 훅 사용하기
  const navigate = useNavigate();

  // 회원가입 함수 정의
  const signup = (email, password, displayName) => {
      setError(null);
      setIsPending(true);
      // Firebase의 createUserWithEmailAndPassword 함수를 사용하여 사용자 생성
      createUserWithEmailAndPassword(appAuth, email, password)
          .then((userCredential) => {
              const user = userCredential.user;
              if (!user) {
                  throw new Error('회원가입에 실패했습니다.');
              }
              // 사용자의 프로필 업데이트
              updateProfile(appAuth.currentUser, { displayName })
                  .then(() => {
                      // 성공적으로 회원가입 후 useAuthContext를 통해 로그인 처리
                      dispatch({ type: 'login', payload: user });
                      setError(null);
                      setIsPending(false);
                      // 회원가입 성공 메시지 띄우고 페이지 이동
                      alert('새로운 회원이 되신 것을 환영합니다.');
                      navigate('/signup', { replace: true });
                  })
                  .catch((err) => {
                      // 프로필 업데이트 실패
                      setError(err.message);
                      setIsPending(false)
                      console.log(err.message);
                      // 회원가입 실패 메시지 띄우고 페이지 이동
                      alert('회원가입에 실패했습니다.');
                      navigate('/signup', { replace: true });
                  });
          })
          .catch((err) => {
              // 회원가입 실패
              setError(err.message);
              setIsPending(false);
              console.log(err.message);
          });
  };
  return { error, isPending, signup };

};

useState를 이용하여 로딩 중, 에러 발생의 상태를 다룰 플래그 변수를 관리하고 있다. error는 오류 메시지를 저장하고, isPending은 회원가입 작업이 진행 중인지 여부를 나타낸다.

기능의 동작 과정에서 발생하는 상태 변화 등은 Context API로 관리한다. 정상적으로 회원가입 절차가 마쳐질 경우 useAuthContext Hook에서 dispatch를 가져와서 필요한 플래그 변수와 데이터를 갱신한다.

회원가입 기능이 호출되면, 파이어베이스에서 제공하는createUserWithEmailAndPassword() 함수를 사용하여 새 사용자를 생성한다. 파이어베이스에서는 사용자 데이터에 이메일과 비밀번호 이외에도 displayName라는 변수를 사용하는데, 더 정밀한 사용자 구분을 위해서 이 부분도 값을 추가해주어야 한다. createUserWithEmailAndPassword() 함수에서는 이메일, 비밀번호를 이용한 사용자 생성만 가능하기 때문에 새 사용자가 생성된 직후에 updateProfile() 함수를 사용하여 displayName를 갱신하도록 하였다.

프로필 업데이트나 회원가입이 실패할 경우 해당 에러 메시지를 setError 함수를 통해 저장하고, isPending 상태를 false로 변경한 뒤 동작을 마친다. 성공 시 환영 메시지를 띄우고, 페이지를 /signup으로 이동시키며, 실패 시 실패 메시지를 띄우고, 페이지를 동일하게 /signup으로 이동시킨다.

코드 평가.

평가 방법, 개인적인 코드 리뷰 및 Chat GPT 사용.

-> 보안 취약. 입력한 비밀번호가 아무런 보호 장비 없이 무방비하게 사용되고 있음. 외부 공격에 매우 취약. 보안 강화 방안을 구상할 것!
-> 이메일을 검증하지 않음. 입력한 이메일 주소가 유효한지 여부를 판단하지 않고 있음. 빈 값을 입력하는 것만 방지되어 있고 이메일 형식에 대한 검증도 되지 않고 있음. 보강 방안 구상할 것.

profile
프론트엔드 개발자를 준비하고 있습니다.

0개의 댓글