개요

Firebase Auth만 사용하는 이유

Firebase Authentication만 사용한다면 정적 페이지로 충분합니다!

  • 정적 페이지로 가능한 것: 로그인/로그아웃, 사용자 기본 정보 (이메일, 이름, 프로필 사진)
  • 동적 페이지가 필요한 경우: 사용자 추가 정보 저장, 복잡한 비즈니스 로직, 서버사이드 검증

언제 동적 페이지를 고려해야 할까요?

동적 페이지 (서버 필요) 고려 시점:

  • 📊 Firestore/Database: 사용자의 추가 정보, 앱 데이터 저장
  • 🔐 서버사이드 검증: 민감한 데이터 처리, 관리자 권한 확인
  • 🤖 복잡한 로직: 결제 처리, 외부 API 연동, 데이터 분석
  • 📧 백그라운드 작업: 이메일 발송, 배치 처리, 스케줄링

현재 가이드 범위: Firebase Auth만 사용하는 정적 페이지 구현


1단계: Firebase 프로젝트 생성 및 기본 설정

1.1 Firebase 프로젝트 생성

  1. Firebase Console 접속
  2. "프로젝트 추가" 클릭
  3. 프로젝트 정보 입력:
프로젝트 이름: bookplay-production
프로젝트 ID: bookplay-production-[자동생성번호]
프로젝트 이름: bookplay-production
프로젝트 ID: bookplay-production
프로젝트 번호: 902982228800
웹 API 키: 이 프로젝트에 웹 API 키가 없습니다.
  1. Google Analytics 설정: 필요에 따라 선택 (Auth만 사용시 불필요)

1.2 프로젝트 정보 확인

생성 완료 후 기록할 정보:

프로젝트 ID: bookplay-production-xxxxx
웹 API 키: AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXX
Auth 도메인: bookplay-production-xxxxx.firebaseapp.com

1.3 필요한 API 활성화 (GCP Console)

  1. Google Cloud Console 접속
  2. 프로젝트 선택: 생성한 Firebase 프로젝트
  3. APIs & Services라이브러리에서 다음 API 활성화:
✅ Firebase Authentication API
✅ Identity and Access Management (IAM) API
API 활성화느 

2단계: Firebase Authentication 설정

2.1 Authentication 서비스 활성화

  1. Authentication시작하기
  2. Sign-in method 탭으로 이동
  3. Google 제공업체 설정:
  4. ✅ 사용 설정 프로젝트 지원 이메일: your-email@gmail.com 프로젝트 공개용 이름: BookPlay Production
  5. 저장 클릭

2.2 웹 앱 등록

  1. 프로젝트 개요앱 추가
  2. 앱 정보 입력:
  3. 앱 닉네임: BookPlay Web App ✅ 이 앱의 Firebase Hosting도 설정합니다 (체크)
  4. 앱 등록 후 설정 정보 복사 및 저장

2.3 승인된 도메인 설정

  1. Authentication설정승인된 도메인
  2. 기본 설정 확인:
✅ localhost (로컬 개발용)
✅ bookplay-production-xxxxx.firebaseapp.com (Firebase Hosting)
✅ bookplay-production-xxxxx.web.app (Firebase Hosting)

3단계: 로컬 테스트를 위한 정적 구성

3.1 프로젝트 구조 생성

# 프로젝트 폴더 생성
mkdir my-auth-app
cd my-auth-app

# 폴더 구조 생성
mkdir public
mkdir public/js
mkdir public/css
mkdir public/assets

최종 구조:

my-auth-app/
├── public/
│   ├── index.html
│   ├── login.html
│   ├── dashboard.html
│   ├── js/
│   │   ├── firebase-config.js
│   │   ├── auth.js
│   │   └── app.js
│   ├── css/
│   │   └── style.css
│   └── assets/
└── firebase.json (나중에 생성)

3.2 Firebase 설정 파일

// public/js/firebase-config.js
import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.0.0/firebase-app.js';
import { getAuth } from 'https://www.gstatic.com/firebasejs/10.0.0/firebase-auth.js';

const firebaseConfig = {
  apiKey: "your-api-key",
  authDomain: "your-project.firebaseapp.com", 
  projectId: "your-project-id",
  storageBucket: "your-project.appspot.com",
  messagingSenderId: "123456789",
  appId: "your-app-id"
};

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

3.3 인증 로직 구현

// public/js/auth.js
import { auth } from './firebase-config.js';
import { 
  GoogleAuthProvider, 
  signInWithPopup, 
  signOut, 
  onAuthStateChanged 
} from 'https://www.gstatic.com/firebasejs/10.0.0/firebase-auth.js';

const googleProvider = new GoogleAuthProvider();

// Google 로그인
export const signInWithGoogle = async () => {
  try {
    const result = await signInWithPopup(auth, googleProvider);
    console.log('로그인 성공:', result.user);
    return result.user;
  } catch (error) {
    console.error('로그인 실패:', error);
    throw error;
  }
};

// 로그아웃
export const logout = async () => {
  try {
    await signOut(auth);
    console.log('로그아웃 성공');
  } catch (error) {
    console.error('로그아웃 실패:', error);
    throw error;
  }
};

// 인증 상태 변화 감지
export const onAuthChange = (callback) => {
  return onAuthStateChanged(auth, callback);
};

// 현재 사용자 정보 가져오기
export const getCurrentUser = () => {
  return auth.currentUser;
};

3.4 메인 앱 로직

// public/js/app.js
import { signInWithGoogle, logout, onAuthChange, getCurrentUser } from './auth.js';

// DOM 요소
const loginBtn = document.getElementById('login-btn');
const logoutBtn = document.getElementById('logout-btn');
const userInfo = document.getElementById('user-info');
const loginSection = document.getElementById('login-section');
const userSection = document.getElementById('user-section');

// 페이지 로드 시 초기화
document.addEventListener('DOMContentLoaded', () => {
  initApp();
});

function initApp() {
  // 이벤트 리스너 등록
  loginBtn?.addEventListener('click', handleLogin);
  logoutBtn?.addEventListener('click', handleLogout);

  // 인증 상태 변화 감지
  onAuthChange((user) => {
    if (user) {
      showUserSection(user);
    } else {
      showLoginSection();
    }
  });
}

// 로그인 처리
async function handleLogin() {
  try {
    loginBtn.disabled = true;
    loginBtn.textContent = '로그인 중...';

    const user = await signInWithGoogle();
    console.log('로그인 성공:', user);
  } catch (error) {
    alert('로그인에 실패했습니다: ' + error.message);
    console.error('로그인 에러:', error);
  } finally {
    loginBtn.disabled = false;
    loginBtn.textContent = 'Google로 로그인';
  }
}

// 로그아웃 처리
async function handleLogout() {
  try {
    logoutBtn.disabled = true;
    logoutBtn.textContent = '로그아웃 중...';

    await logout();
    console.log('로그아웃 성공');
  } catch (error) {
    alert('로그아웃에 실패했습니다: ' + error.message);
    console.error('로그아웃 에러:', error);
  } finally {
    logoutBtn.disabled = false;
    logoutBtn.textContent = '로그아웃';
  }
}

// 사용자 섹션 표시
function showUserSection(user) {
  if (loginSection) loginSection.style.display = 'none';
  if (userSection) userSection.style.display = 'block';

  if (userInfo) {
    userInfo.innerHTML = `
      <div class="user-profile">
        <img src="${user.photoURL || '/assets/default-avatar.png'}" 
             alt="프로필" class="profile-img">
        <div class="user-details">
          <h2>환영합니다, ${user.displayName || '사용자'}님!</h2>
          <p class="user-email">${user.email}</p>
          <p class="user-id">UID: ${user.uid}</p>
        </div>
      </div>
    `;
  }
}

// 로그인 섹션 표시
function showLoginSection() {
  if (loginSection) loginSection.style.display = 'block';
  if (userSection) userSection.style.display = 'none';
}

3.5 HTML 파일

<!-- public/index.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Firebase Auth Test</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <div class="container">
        <header>
            <h1>🔥 Firebase Auth 테스트</h1>
        </header>

        <div id="login-section" class="auth-section">
            <h2>로그인이 필요합니다</h2>
            <p>Google 계정으로 로그인해주세요.</p>
            <button id="login-btn" class="btn btn-google">
                <span>🔑 Google로 로그인</span>
            </button>
        </div>

        <div id="user-section" class="auth-section" style="display: none;">
            <div id="user-info"></div>
            <div class="actions">
                <button id="logout-btn" class="btn btn-secondary">로그아웃</button>
            </div>
        </div>
    </div>

    <script type="module" src="js/app.js"></script>
</body>
</html>

3.6 스타일 파일

/* public/css/style.css */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
}

.container {
    background: white;
    border-radius: 20px;
    padding: 40px;
    box-shadow: 0 20px 40px rgba(0,0,0,0.1);
    max-width: 500px;
    width: 90%;
    text-align: center;
}

header h1 {
    color: #333;
    margin-bottom: 30px;
    font-size: 2rem;
}

.auth-section {
    padding: 20px 0;
}

.auth-section h2 {
    color: #555;
    margin-bottom: 15px;
}

.auth-section p {
    color: #666;
    margin-bottom: 25px;
    line-height: 1.6;
}

.btn {
    padding: 15px 30px;
    border: none;
    border-radius: 50px;
    font-size: 16px;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.3s ease;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 200px;
}

.btn:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

.btn-google {
    background: #4285f4;
    color: white;
}

.btn-google:hover:not(:disabled) {
    background: #3367d6;
    transform: translateY(-2px);
    box-shadow: 0 5px 15px rgba(66, 133, 244, 0.3);
}

.btn-secondary {
    background: #6c757d;
    color: white;
    margin-top: 20px;
}

.btn-secondary:hover:not(:disabled) {
    background: #545b62;
    transform: translateY(-2px);
}

.user-profile {
    display: flex;
    align-items: center;
    text-align: left;
    background: #f8f9fa;
    padding: 20px;
    border-radius: 15px;
    margin-bottom: 20px;
}

.profile-img {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    margin-right: 20px;
    border: 3px solid #4285f4;
}

.user-details h2 {
    color: #333;
    margin-bottom: 5px;
    font-size: 1.4rem;
}

.user-email {
    color: #666;
    font-size: 1rem;
    margin-bottom: 5px;
}

.user-id {
    color: #999;
    font-size: 0.8rem;
    font-family: monospace;
}

.actions {
    text-align: center;
}

@media (max-width: 600px) {
    .container {
        padding: 30px 20px;
    }

    .user-profile {
        flex-direction: column;
        text-align: center;
    }

    .profile-img {
        margin-right: 0;
        margin-bottom: 15px;
    }
}

3.7 로컬 테스트 실행

# 간단한 HTTP 서버 실행 (여러 방법 중 선택)

# 방법 1: Python (가장 간단)
cd public
python -m http.server 8000
# 브라우저에서 http://localhost:8000 접속

# 방법 2: Node.js (npx 사용)
cd public  
npx serve -s . -p 8000

# 방법 3: Live Server (VS Code 확장)
# VS Code에서 index.html 우클릭 → "Open with Live Server"

테스트 체크리스트:

  • 페이지가 정상 로드되는지
  • Google 로그인 버튼 클릭 시 팝업이 뜨는지
  • 로그인 성공 후 사용자 정보가 표시되는지
  • 로그아웃이 정상 작동하는지
  • 브라우저 개발자 도구에서 에러가 없는지

4단계: GCP에 정적 배포

4.1 Firebase CLI 설치 및 로그인

# Firebase CLI 설치
npm install -g firebase-tools

# Firebase 로그인
firebase login

# 로그인 확인
firebase projects:list

4.2 Firebase 프로젝트 초기화

# 프로젝트 루트 폴더에서 실행
firebase init hosting

# 설정 선택
? Select a default Firebase project: [생성한 프로젝트 선택]
? What do you want to use as your public directory? public
? Configure as a single-page app (rewrite all urls to /index.html)? Yes
? Set up automatic builds and deploys with GitHub? No
? File public/index.html already exists. Overwrite? No

4.3 Firebase 설정 파일

{
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [ {
      "source": "**",
      "destination": "/index.html"
    } ],
    "headers": [ {
      "source": "**/*.@(eot|otf|ttf|ttc|woff|font.css)",
      "headers": [ {
        "key": "Access-Control-Allow-Origin",
        "value": "*"
      } ]
    }, {
      "source": "**/*.@(js|css)",
      "headers": [ {
        "key": "Cache-Control",
        "value": "max-age=604800"
      } ]
    } ]
  }
}

4.4 배포 실행

# 배포 전 로컬 프리뷰 (선택사항)
firebase serve --only hosting

# 실제 배포
firebase deploy --only hosting

# 배포 완료 후 표시되는 URL들:
# ✔ Deploy complete!
# Project Console: https://console.firebase.google.com/project/your-project/overview
# Hosting URL: https://your-project.web.app

4.5 배포 테스트

배포된 URL에서 다음 사항 확인:

  • HTTPS로 정상 접속되는지
  • Google 로그인이 작동하는지
  • 모바일에서도 정상 작동하는지
  • 새로고침 후에도 로그인 상태가 유지되는지

4.6 배포 URL 관리

# 현재 배포된 사이트 정보 확인
firebase hosting:sites:list

# 커스텀 도메인 추가 (선택사항)
firebase hosting:sites:create your-custom-domain

# 배포 히스토리 확인
firebase hosting:clone your-project:source your-project:dest

+ Recent posts