파이어베이스를 이용한 초간단 로그인/회원가입 구현 (feat. Firestore Database)

LIMHALIM·2025년 3월 6일
4


🌵 개발 환경

React 19.0.0
TypeScript 5.7.3
Firebase 11.3.1


파이어베이스 세팅

1️⃣ 파이어베이스
에 접속하여 프로젝트를 생성하고, React에 Firebase SDK를 추가하기 위해 Firebase를 설치한다.

npm i firebase

2️⃣ src 폴더 안에 firebase.tsx 파일을 생성하여 아래 내용을 넣어준다. (프로젝트 설정 탭에서 복사할 수 있다.)

import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';

interface ImportMetaEnv {
  VITE_FIREBASE_APP_KEY: string;
  VITE_FIREBASE_AUTH_DOMAIN: string;
  VITE_FIREBASE_PROJECT_ID: string;
  VITE_FIREBASE_STORAGE_BUCKET: string;
  VITE_FIREBASE_MESSAGING_SENDER_ID: string;
  VITE_FIREBASE_APP_ID: string;
  VITE_FIREBASE_MEASUREMENTID: string;
}

declare global {
  interface ImportMeta {
    readonly env: ImportMetaEnv;
  }
}

const firebaseConfig = {
  apiKey: import.meta.env.VITE_FIREBASE_APP_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,
  measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENTID,
};

// Initialize Firebase
export const app = initializeApp(firebaseConfig);

export const auth = getAuth(app); // 인증 객체
export const db = getFirestore(app); // 데이터베이스

회원가입


1️⃣ 회원가입 기능을 구현하기 위해 Authentication - 시작하기를 클릭한다.


2️⃣ 로그인 방법을 추가하라는 창이 뜰텐데, 나는 이메일/비밀번호를 추가하여 사용 설정을 활성화해줬다.

자 이제 코드로 다시 돌아와 회원가입을 구현해보자.

파이어베이스 공식 문서를 보면, 신규 사용자 가입의 경우 createUserWithEmailAndPassword 메서드를 제공하고 있다고 한다.

실제 작성한 코드를 기반으로 설명을 해보자면,
(참고로, 나는 react-hook-form 라이브러리를 사용하여 form을 구현하였다.)

  import { FirebaseError } from 'firebase/app';
  import { auth } from '@/firebase';
  import { createUserWithEmailAndPassword } from 'firebase/auth';

  const handleSignUp = async (data: SignUpType) => {
    try {
      await createUserWithEmailAndPassword(
        auth,
        data.email,
        data.password
      );

      navigate('/');
    } catch (error) {
      const firebaseError = error as FirebaseError;

      switch (firebaseError.code) {
        case 'auth/email-already-in-use':
          setError('email', { message: '이미 가입된 이메일입니다.' });
          break;
        case 'auth/invalid-email':
          setError('email', { message: '이메일 형식이 잘못되었습니다.' });
          break;
        case 'auth/weak-password':
          setError('password', {
            message: '비밀번호를 6자 이상 입력해 주세요.',
          });
          break;
        default:
          setError('email', {
            message: '회원가입 중 문제가 발생했습니다. 다시 시도해 주세요.',
          });
          break;
      }
    }
  };
  • 파이어베이스의 인증 객체를 가져오기 위해 firebase.tsx에서 getAuth 메서드를 사용하여 export 해준 auth를 가져온다.

    export const auth = getAuth(app);
  • 입력받은 사용자의 이메일 주소와 비밀번호를 createUserWithEmailAndPassword 메서드의 인자로 넣어주어 유저를 생성한다.

      await createUserWithEmailAndPassword(
            auth,
            data.email,
            data.password
          );
  • 함수가 비동기로 실행되게 만들고, 회원가입 시에 발생할 수 있는 오류를 처리하기 위해 try-catch 구문을 사용하였다. 파이어베이스에서는 사용자가 입력한 이메일 주소와 비밀번호의 유효성을 검사하여 적절한 message를 제공해주고 있다. (자세한 오류 코드 목록은 여기서 확인할 수 있다.)

폼을 제출하게 되면 사용자가 생성되어 Authentication - 사용자 탭에 정상적으로 저장이 된 것을 확인할 수 있다!

💡 여기서 추가로 알고 있어야 할 점은, Firebase에서는 사용자가 회원가입을 하면 해당 사용자로 자동 로그인이 된다는 것이다. 즉, 회원가입 직후 추가적인 로그인 절차 없이도 사용자가 인증된 상태로 앱을 이용할 수 있도록 설계되어 있다.

유저 상세 정보 저장

파이어베이스에서는 사용자의 표시 이름 및 프로필 사진 URL 등의 기본 프로필 정보를 업데이트할 수 있도록 updateProfile 메서드를 제공해준다. 이를 통해 사용자 정보를 업데이트할 수 있다.

await createUserWithEmailAndPassword(
        auth,
        data.email,
        data.password
      );

if (createdUser.user) {
   await updateProfile(auth.currentUser, { displayName: "Jane Q. User", photoURL: "https://example.com/jane-q-user/profile.jpg"});
}

(currentUset 속성을 통해 현재 로그인한 사용자 정보를 가져올 수 있다. 사용자가 로그인 상태가 아니라면 currentUser 값이 null이다.)


데이터베이스 사용하여 사용자 정보 저장하기

위처럼 Firebase Authentication만 사용하여 회원가입을 진행하게 되면, 이메일/비밀번호로만 회원가입이 이루어지기 때문에 이외에 커스텀한 정보들을 저장할 수 없게 된다. 😣

그래서,
Firestore Database를 통해 데이터베이스에 추가로 정보를 저장할 수 있다.

데이터베이스 시작하기를 클릭하여 데이터베이스를 테스트모드에서 시작한다. (데이터베이스의 위치는 현재 위치에서 가장 가까운 곳을 선택하면 된다.)

내 프로젝트에서는 직책, 부서, 입사일 등의 정보를 특정 사용자마다 추가로 저장을 해줘야 했기에 사용자에게 입력받은 위 정보들을 데이터베이스에 setDoc 메서드를 통해 저장을 해주었다. (getDoc, addDoc, updateDoc, deleteDocCRUD 관련 메서드는 파이어베이스 공식 문서를 통해 더 자세히 확인해보자!)

 try {
      const createdUser = await createUserWithEmailAndPassword(
        auth,
        data.email,
        data.password
      );

      if (createdUser.user) {
        await updateProfile(createdUser.user, { displayName: data.name });
      }

      const user = createdUser.user;

      const userData = {
        name: user.displayName,
        email: user.email,
        position: data.position, // 직책
        department: data.department, // 부서
        joinDate: data.joinDate, // 입사일
      };

      // Firestore에 유저 정보 저장
      await setDoc(doc(db, 'users', user.uid), userData);

      navigate('/');
    } catch (error) {
      // 예외 처리
    }

이번에도 Firestore Database - 데이터 탭에 정상적으로 저장이 된 것을 확인할 수 있다!


로그인

이제 회원가입을 마쳤으니, 로그인을 진행해보자.

로그인의 경우 signInWithEmailAndPassword 메서드를 통해 로그인을 진행할 수 있다고 한다.

이번에도 실제 작성한 코드를 기반으로 설명해보자면,

try {
  await signInWithEmailAndPassword(
        auth,
        data.email,
        data.password
      );

  navigate('/');
} catch (error) {
  • 회원가입과 마찬가지로 예외를 처리하기 위해 try-catch 구문을 사용하였고, 입력받은 사용자의 이메일 주소와 비밀번호를 signInWithEmailAndPassword 메서드의 인자로 넣어주어 로그인을 진행한다.

그렇다면 로그인된 유저의 상태는 언제까지 유효할까? 🤔

Firebase는 기본적으로 사용자가 명시적으로 로그아웃하지 않는 한, 인증 상태를 지속적으로 유지한다고 한다. 이는 사용자가 브라우저를 닫거나 페이지를 새로고침해도 로그인 상태가 유지됨을 의미한다. 이러한 동작은 Firebase의 인증 상태 지속성 설정에 따라 관리되며, 기본 설정은 local로, 영구적으로 인증 상태를 유지한다. 따라서, 로그아웃을 통해서만 로그인 상태가 해제된다.

(로그아웃은 auth.signOut()로 아주 쉽게 처리할 수 있다.)


로그인 접근 제한

추가로, onAuthStateChanged 메서드를 통해 로그인 상태 변경을 감지하여 로그인 접근 제한 로직을 구현하였다.

import { JSX, ReactNode, useEffect, useState } from 'react';

import { Navigate } from 'react-router-dom';
import { onAuthStateChanged, User } from 'firebase/auth';
import { auth } from '@/firebase';

interface AuthRouteProps {
  children: ReactNode;
  type: 'protected' | 'public';
}

export const AuthRoute = ({ children, type }: AuthRouteProps): JSX.Element => {
  const [isLoading, setIsLoading] = useState(true);
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      setUser(user);
      setIsLoading(false);
    });

    return () => unsubscribe();
  }, []);

  if (isLoading) {
    return <div>Loading...</div>; // 임시 로딩
  }

  if (type === 'protected' && !user) {
    return <Navigate to="/login" replace />;
  }

  if (type === 'public' && user) {
    return <Navigate to="/" replace />;
  }

  return <>{children}</>;
};
  • type="protected" → 로그인된 유저만 접근 가능, 아니면 로그인 페이지로 이동
  • type="public" → 로그인 안 한 유저만 접근 가능, 아니면 홈으로 이동

라우트 설정하기

     <Route
          path="/login"
          element={
            <AuthRoute type="public">
              <LoginPage />
            </AuthRoute>
          }
        />
        <Route
          path="/signup"
          element={
            <AuthRoute type="public">
              <SignUpPage />
            </AuthRoute>
          }
        />
        <Route
          element={
            <AuthRoute type="protected">
              <NavBar />
            </AuthRoute>
          }
        >

currentUser 속성을 사용하지 않고 왜 onAuthStateChanged를 사용했을까?

currentUseronAuthStateChanged 모두 Firebase Authentication에서 사용자 정보를 가져오는 방법이지만, 사용하는 상황과 안전성에서 차이점이 있다.

1. auth.currentUser의 동작 방식

auth.currentUser는 현재 로그인된 사용자 정보를 가져오는 속성이다. 하지만, 즉시 값이 설정되지 않을 수도 있다고 한다. Firebase는 앱이 처음 로드될 때, 사용자의 인증 상태를 비동기적으로 복원하기 때문에, auth.currentUser가 null일 수도 있다는 것이다.

그렇다면 언제 사용해야 할까?

사용자가 이미 로그인한 상태일 때 즉시 currentUser를 사용해서 정보를 업데이트하는 경우이다.
예를 들어, 사용자가 계정을 생성한 후 바로 프로필을 업데이트할 때는 auth.currentUser를 사용해도 문제가 없다.

2. onAuthStateChanged를 사용하는 이유

하지만 앱이 처음 실행되었을 때 로그인 상태를 정확하게 감지하려면 onAuthStateChanged를 사용하는 것이 안전하다. Firebase가 사용자 인증 상태를 비동기적으로 복원하는 과정이 끝난 후, 로그인 상태가 변할 때마다 감지하는 역할을 하기 때문이다.

사용하면 적절한 경우

  • 앱이 처음 로드될 때 로그인 상태를 확인해야 할 때
  • 페이지 이동(로그인/로그아웃 접근 제한)을 구현할 때

onAuthStateChanged는 Firebase가 인증 정보를 완전히 불러온 후 실행되므로, 로그인 상태가 정확하게 반영된다.


참고문헌

https://firebase.google.com/docs/auth?hl=ko
https://jjang-j.tistory.com/48

profile
모든 익숙함에 물음표 더하기

0개의 댓글