Choorai
Lv.2 Google

Firebase Auth 가이드

Firebase Authentication은 Google의 무료 인증 서비스입니다. 웹과 모바일 앱 모두에서 사용할 수 있습니다.

왜 Firebase Auth인가요?

  • 무료 티어: MAU 무제한
  • Google, Facebook, Apple 등 소셜 로그인 지원
  • 이메일/비밀번호, 전화번호 인증 지원
  • Firebase의 다른 서비스(Firestore, Storage)와 통합
  • 모바일 앱(Flutter, React Native)에도 동일한 API

1Firebase 프로젝트 설정

  1. Firebase Console 에서 프로젝트 생성
  2. Authentication 클릭 → 시작하기
  3. Sign-in method에서 원하는 로그인 방식 활성화:
    • 이메일/비밀번호
    • Google
    • GitHub (OAuth 설정 필요)
  4. 프로젝트 설정 → 일반 → 웹 앱 추가
  5. Firebase 설정 정보 복사

2React 앱 연동

터미널
npm install firebase

환경 변수 설정

.env.local
# .env.local
VITE_FIREBASE_API_KEY=xxx
VITE_FIREBASE_AUTH_DOMAIN=your-app.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=your-app
VITE_FIREBASE_STORAGE_BUCKET=your-app.appspot.com
VITE_FIREBASE_MESSAGING_SENDER_ID=xxx
VITE_FIREBASE_APP_ID=xxx

Firebase 초기화

lib/firebase.ts
// lib/firebase.ts
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';

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

const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);

3이메일/비밀번호 인증

components/EmailAuth.tsx
// components/EmailAuth.tsx
import { useState } from 'react';
import {
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
} from 'firebase/auth';
import { auth } from '../lib/firebase';

export function EmailAuth() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [isLogin, setIsLogin] = useState(true);
  const [error, setError] = useState('');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setError('');

    try {
      if (isLogin) {
        await signInWithEmailAndPassword(auth, email, password);
      } else {
        await createUserWithEmailAndPassword(auth, email, password);
      }
    } catch (err: any) {
      setError(err.message);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="이메일"
        className="w-full px-4 py-2 border rounded-lg"
        required
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="비밀번호 (6자 이상)"
        className="w-full px-4 py-2 border rounded-lg"
        required
        minLength={6}
      />
      {error && <p className="text-red-500 text-sm">{error}</p>}
      <button
        type="submit"
        className="w-full py-2 bg-blue-600 text-white rounded-lg"
      >
        {isLogin ? '로그인' : '회원가입'}
      </button>
      <button
        type="button"
        onClick={() => setIsLogin(!isLogin)}
        className="w-full text-sm text-gray-600"
      >
        {isLogin ? '계정이 없으신가요? 회원가입' : '이미 계정이 있으신가요? 로그인'}
      </button>
    </form>
  );
}

4Google 소셜 로그인

components/GoogleLogin.tsx
// components/GoogleLogin.tsx
import { signInWithPopup, GoogleAuthProvider } from 'firebase/auth';
import { auth } from '../lib/firebase';

const googleProvider = new GoogleAuthProvider();

export function GoogleLogin() {
  const handleGoogleLogin = async () => {
    try {
      const result = await signInWithPopup(auth, googleProvider);
      // result.user에 사용자 정보
      console.log('로그인 성공:', result.user);
    } catch (error: any) {
      console.error('로그인 실패:', error.message);
    }
  };

  return (
    <button
      onClick={handleGoogleLogin}
      className="flex items-center gap-2 px-4 py-2 bg-white border rounded-lg"
    >
      <svg className="w-5 h-5" viewBox="0 0 24 24">
        <path
          fill="#4285F4"
          d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
        />
        <path
          fill="#34A853"
          d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
        />
        <path
          fill="#FBBC05"
          d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
        />
        <path
          fill="#EA4335"
          d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
        />
      </svg>
      Google로 계속하기
    </button>
  );
}

정보

Firebase Console에서 Google 로그인을 먼저 활성화해야 합니다. GitHub, Facebook 등 다른 프로바이더도 비슷한 방식으로 추가할 수 있습니다.

5인증 상태 관리 (커스텀 훅)

onAuthStateChanged로 로그인 상태 변화를 감지합니다.

hooks/useAuth.ts
// hooks/useAuth.ts
import { useState, useEffect } from 'react';
import { User, onAuthStateChanged, signOut } from 'firebase/auth';
import { auth } from '../lib/firebase';

export function useAuth() {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

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

    // 컴포넌트 언마운트 시 구독 해제
    return () => unsubscribe();
  }, []);

  const logout = async () => {
    await signOut(auth);
  };

  return {
    user,
    loading,
    isAuthenticated: !!user,
    logout,
  };
}

Context로 전역 상태 관리

contexts/AuthContext.tsx
// contexts/AuthContext.tsx
import { createContext, useContext, ReactNode } from 'react';
import { User } from 'firebase/auth';
import { useAuth } from '../hooks/useAuth';

interface AuthContextType {
  user: User | null;
  loading: boolean;
  isAuthenticated: boolean;
  logout: () => Promise<void>;
}

const AuthContext = createContext<AuthContextType | null>(null);

export function AuthProvider({ children }: { children: ReactNode }) {
  const auth = useAuth();

  return (
    <AuthContext.Provider value={auth}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuthContext() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuthContext must be used within AuthProvider');
  }
  return context;
}

// main.tsx에서 사용
// <AuthProvider>
//   <App />
// </AuthProvider>

6보호된 라우트 구현

components/ProtectedRoute.tsx
// components/ProtectedRoute.tsx
import { Navigate } from 'react-router-dom';
import { useAuthContext } from '../contexts/AuthContext';

interface ProtectedRouteProps {
  children: React.ReactNode;
}

export function ProtectedRoute({ children }: ProtectedRouteProps) {
  const { isAuthenticated, loading } = useAuthContext();

  if (loading) {
    return <div>로딩 중...</div>;
  }

  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }

  return <>{children}</>;
}

// 사용 예시
// <Route
//   path="/dashboard"
//   element={
//     <ProtectedRoute>
//       <Dashboard />
//     </ProtectedRoute>
//   }
// />

사용자 프로필 표시

components/UserProfile.tsx
// components/UserProfile.tsx
import { useAuthContext } from '../contexts/AuthContext';

export function UserProfile() {
  const { user, logout, isAuthenticated } = useAuthContext();

  if (!isAuthenticated) {
    return null;
  }

  return (
    <div className="flex items-center gap-4 p-4 bg-white rounded-lg shadow">
      {user?.photoURL && (
        <img
          src={user.photoURL}
          alt={user.displayName || '프로필'}
          className="w-12 h-12 rounded-full"
        />
      )}
      <div>
        <p className="font-bold">{user?.displayName || '사용자'}</p>
        <p className="text-sm text-gray-600">{user?.email}</p>
      </div>
      <button
        onClick={logout}
        className="ml-auto px-4 py-2 bg-red-600 text-white rounded-lg"
      >
        로그아웃
      </button>
    </div>
  );
}

언제 Firebase Auth를 선택할까요?

Firebase Auth 추천

  • Firebase 생태계 사용 중 (Firestore 등)
  • 웹 + 모바일 앱 동시 개발
  • 무료로 무제한 사용자 필요
  • Google 로그인이 주요 방식

다른 선택지 고려

  • 빠른 UI 필요 → Clerk
  • Next.js 전용 → NextAuth
  • RBAC/Enterprise → Auth0
  • PostgreSQL 필요 → Supabase Auth

다음 단계

마지막 업데이트: 2026년 2월 22일 · 버전: v0.0.1

피드백 보내기

입력한 내용으로 새 이슈 페이지를 엽니다.

GitHub 이슈로 보내기