주요 포인트 한눈에 보기
이 글은 완성된 포트폴리오 코드를 기준으로 Next.js + TypeScript + Firebase를 사용할 때
어떤 방식으로 제작되었는지 프로젝트를 분석합니다.
그 중 이번 챕터에서는 Firebase Authentication 구조를 분석합니다.
- 포트폴리오: STYNA
- 1편: Firebase 설계와 구조 분석
- 2편: Firebase Auth 설정 분석
- 3편: Firebase Storage 설정 분석
- 4편: Firebase Auth Context 구조 분석
- 5편: Firebase Functions 구조 분석
- 6편: Firebase 규칙 작성법
전체 인증 유틸 코드 구조
이 파일은 Firebase Authentication과 직접 통신하는 모든 로직을 한 곳에 모아둔 인증 유틸 파일입니다.
컴포넌트에서는 이 파일의 함수를 호출만 하도록 설계하여,
UI와 인증 로직을 명확히 분리하는 것을 목표로 했습니다.
// src/shared/libs/firebase/auth.ts
import { auth } from "./firebase";
import {
browserSessionPersistence,
createUserWithEmailAndPassword,
setPersistence,
signInWithEmailAndPassword,
signOut,
sendPasswordResetEmail,
confirmPasswordReset,
verifyPasswordResetCode
} from "firebase/auth";
export async function signUp(email: string, password: string) {
return createUserWithEmailAndPassword(auth, email, password);
}
export async function loginKeepAlive(email: string, password: string) {
await setPersistence(auth, browserSessionPersistence);
return signInWithEmailAndPassword(auth, email, password);
}
export async function loginOneSession(email: string, password: string) {
await setPersistence(auth, browserSessionPersistence);
return signInWithEmailAndPassword(auth, email, password);
}
export async function logout() {
return signOut(auth);
}
export async function sendPasswordReset(email: string) {
return sendPasswordResetEmail(auth, email);
}
export async function verifyResetCode(code: string) {
return verifyPasswordResetCode(auth, code);
}
export async function confirmPasswordResetWithCode(code: string, newPassword: string) {
return confirmPasswordReset(auth, code, newPassword);
}
해당 포트폴리오에 대해 설명하며, 아래에서는 이 흐름을 위에서 아래로 자연스럽게 따라가며 하나씩 설명합니다.
Auth의 모든 사용 방법과 옵션은 이 문서에서 모두 다루지 않습니다.
세부 기능은 아래 공식 문서를 기준으로 학습하시면 됩니다.
회원가입과 로그인 처리 흐름
Firebase Authentication에서 가장 기본이 되는 기능은 이메일과 비밀번호 기반 인증입니다.
회원가입은 Firebase가 내부적으로 계정을 생성하고 관리하며,
로그인 시에는 해당 계정 정보를 검증한 뒤 인증 세션을 생성합니다.
export async function signUp(email: string, password: string) {
return createUserWithEmailAndPassword(auth, email, password);
}
이 함수는 사용자가 입력한 이메일과 비밀번호를 Firebase로 전달하여
새로운 사용자 계정을 생성합니다.
성공하면 UserCredential 객체가 반환되며,
실패 시에는 Firebase에서 정의한 인증 에러가 발생합니다.
// 로그인 유지(브라우저 닫아도 유지)
export async function loginKeepAlive(email: string, password: string) {
await setPersistence(auth, browserLocalPersistence);
return signInWithEmailAndPassword(auth, email, password);
}
// 창 닫으면 로그아웃되는 로그인
export async function loginOneSession(email: string, password: string) {
await setPersistence(auth, browserSessionPersistence);
return signInWithEmailAndPassword(auth, email, password);
}
이 두 로그인 함수는 이미 Firebase에 가입되어 있는 계정을 대상으로 인증을 수행합니다.
회원가입과 달리 새로운 사용자를 생성하지 않고,
Firebase에 저장된 이메일·비밀번호 정보를 검증해 로그인 세션을 만드는 역할을 합니다.
여기서 가장 중요한 흐름은 로그인 이전에 setPersistence를 먼저 호출한다는 점입니다.
Firebase Authentication은 로그인에 성공하는 순간,
해당 사용자의 인증 정보를 브라우저 어딘가에 저장합니다.
이때 어디에 저장할지를 미리 정해두는 설정이 바로 persistence입니다.
setPersistence는 “로그인 방식을 바꾼다”기보다는,
로그인 결과가 유지되는 장소를 지정하는 설정에 가깝습니다.
이 설정이 끝난 이후에 signInWithEmailAndPassword가 호출되어야
Firebase는 지정된 저장소 규칙에 맞춰 인증 정보를 기록합니다.
만약 signInWithEmailAndPassword를 먼저 호출하고
그 이후에 setPersistence를 실행하면,
이미 생성된 로그인 세션에는 해당 설정이 적용되지 않습니다.
그래서 이 코드에서는 반드시 persistence 설정 → 로그인 순서를 유지합니다.
현재 코드에서는 두 로그인 함수가 서로 다른 persistence 전략을 사용합니다.
이를 통해 로그인 유지 정책의 차이를 코드 레벨에서 명확히 드러내고 있습니다.
loginKeepAlive 함수는 browserLocalPersistence를 사용합니다.
이 설정은 인증 정보를 브라우저 로컬 스토리지에 저장하며,
브라우저를 완전히 종료했다가 다시 실행해도 로그인 상태가 복원됩니다.
개인 PC 환경이나 “자동 로그인” UX가 필요한 서비스에 적합한 방식입니다.
반대로 loginOneSession 함수는 browserSessionPersistence를 사용합니다.
이 설정은 인증 정보를 현재 브라우저 탭의 세션에만 유지하며,
탭이나 브라우저를 닫는 순간 즉시 로그아웃됩니다.
공용 PC, 보안이 중요한 환경에서 의도적으로 사용되는 방식입니다.
이처럼 함수 이름과 persistence 설정을 1:1로 대응시켜 설계함으로써,
컴포넌트에서는 “어떤 로그인 정책을 쓸 것인가”만 선택하면 되고,
내부 저장 방식에 대한 세부 구현은 알 필요가 없도록 추상화되어 있습니다.
로그인 유지 방식과 persistence 설정
여기서 가장 많이 오해하는 부분이 바로 “이 코드만 있으면 로그인 유지가 자동으로 되는가?”입니다.
결론부터 말하면, setPersistence만으로 로그인 유지 로직이 완성되는 것은 아닙니다.
persistence는 인증 정보를 어디에 저장할지를 정하는 설정일 뿐이며,
실제 로그인 상태를 화면과 로직에 반영하려면 추가 처리가 필요합니다.
Firebase는 로그인 성공 시 인증 정보를 내부 저장소(session 또는 local)에 저장하고,
페이지 새로고침이나 앱 재실행 시 해당 정보를 자동으로 복원합니다.
하지만 이 상태를 UI에서 사용하려면
onAuthStateChanged 또는 현재 사용자(auth.currentUser)를 기준으로
로그인 여부를 다시 판단해야 합니다.
// 예시: 앱 초기화 시 로그인 상태 확인
onAuthStateChanged(auth, (user) => {
if (user) {
// 로그인 유지 상태
} else {
// 로그아웃 상태
}
});
즉, 이 포트폴리오 코드에서 setPersistence는
“로그인 정보를 유지할 수 있는 조건을 만들어 주는 역할”을 담당하고,
실제 로그인 유지 UX는 인증 상태 감지 로직과 함께 완성됩니다.
'use client';
import { useState, useEffect } from "react";
import { onAuthStateChanged, User } from "firebase/auth";
import { auth } from "@/shared/libs/firebase/firebase";
export function useAuthUser() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setUser(user);
setLoading(false);
});
return () => unsubscribe();
}, []);
return { user, loading };
}
해당 코드는 포트폴리오에서 사용한 코드입니다.
커스텀 훅을 만들어서 로그인 여부를 확인하였습니다.
로그아웃과 인증 상태 초기화
로그아웃은 단순히 “사용자를 내보낸다”라는 개념이 아니라,
현재 Firebase Authentication 인스턴스(auth)에 저장되어 있는
모든 인증 정보를 명확히 제거하는 작업입니다.
Firebase Auth는 로그인에 성공하면 내부적으로 토큰과 사용자 정보를 저장하고,
이를 기준으로 로그인 상태를 유지합니다.
로그아웃은 이 저장된 정보를 삭제하여,
Firebase 입장에서 더 이상 인증된 사용자가 없다고 판단하게 만드는 과정입니다.
export async function logout() {
return signOut(auth);
}
signOut 함수는 별도의 파라미터 없이 auth 인스턴스만 전달하면 됩니다.
이 한 줄의 코드 안에서 Firebase는 다음 작업을 수행합니다.
1) 현재 사용자 세션 제거
2) 저장된 인증 토큰 삭제
3) 인증 상태를 “로그아웃”으로 변경
이 과정이 완료되면,
onAuthStateChanged 리스너를 사용 중인 모든 영역에서
user 값이 null로 변경됩니다.
따라서 별도의 상태 업데이트 코드를 작성하지 않아도
UI는 자동으로 로그아웃 상태로 전환될 수 있습니다.
이 구조 덕분에 로그아웃 이후
페이지 이동, 보호된 라우트 차단, 로그인 페이지 리다이렉트 같은 처리를
인증 상태 변화 하나만으로 제어할 수 있습니다.
비밀번호 재설정 전체 흐름
비밀번호 재설정은 이메일 기반으로 동작합니다.
사용자는 이메일로 전달된 링크를 통해 인증 코드를 검증하고,
새로운 비밀번호를 설정하게 됩니다.
export async function sendPasswordReset(email: string) {
return sendPasswordResetEmail(auth, email);
}
이 함수는 사용자가 입력한 이메일 주소로
비밀번호 재설정 링크를 전송합니다.
이 단계에서는 사용자가 실제로 존재하는지 여부만 확인하며,
비밀번호 변경은 아직 이루어지지 않습니다.
export async function verifyResetCode(code: string) {
return verifyPasswordResetCode(auth, code);
}
사용자가 이메일 링크를 클릭하면
URL에 포함된 인증 코드(oobCode)가 전달됩니다.
이 함수는 해당 코드가 유효한지,
만료되거나 이미 사용된 코드는 아닌지를 검증하는 역할을 합니다.
export async function confirmPasswordResetWithCode(code: string, newPassword: string) {
return confirmPasswordReset(auth, code, newPassword);
}
마지막 단계에서는 검증이 완료된 코드와
사용자가 새로 입력한 비밀번호를 전달하여
실제 비밀번호 변경을 수행합니다.
FAQ
Q. 인증 로직을 컴포넌트 내부가 아니라 별도의 유틸 파일로 분리한 이유는 무엇인가요?
인증 로직을 컴포넌트 내부에 직접 작성하면 UI 코드와 비즈니스 로직이 섞이게 됩니다.
이 포트폴리오에서는 인증 흐름을 하나의 책임으로 분리하여,
컴포넌트는 사용자 입력과 화면 제어만 담당하고,
실제 인증 처리는 유틸 함수에서 일관되게 관리하도록 설계했습니다.
Q. setPersistence를 로그인 함수 안에서 매번 호출해도 문제가 없나요?
setPersistence는 인증 방식이 적용되기 전에 호출되어야 하므로,
signInWithEmailAndPassword 이전에만 실행된다면 문제가 없습니다.
다만 실제 서비스에서는 로그인 정책이 고정된 경우,
앱 초기화 시 한 번만 설정하는 구조도 고려할 수 있습니다.
Q. loginKeepAlive와 loginOneSession을 굳이 나눈 이유는 무엇인가요?
로그인 유지 정책은 UX와 보안 요구사항에 따라 달라질 수 있기 때문입니다.
포트폴리오에서는 이 차이를 코드 레벨에서 명확히 드러내기 위해
로그인 함수를 분리하여 설계 의도를 표현했습니다.
Q. 비밀번호 재설정 과정을 세 단계로 나눈 이유는 무엇인가요?
이메일 전송, 코드 검증, 비밀번호 변경은 각각 실패 원인과 UX가 다릅니다.
이 과정을 하나로 묶으면 에러 처리와 사용자 안내가 불명확해지기 때문에,
단계별로 분리하여 제어 흐름을 명확히 했습니다.
Q. 이 문서만 보고 Firebase Auth를 전부 이해해도 되나요?
이 글은 포트폴리오 코드의 설계 의도를 설명하는 문서입니다.
Firebase Authentication의 모든 옵션과 예외 케이스를 포함하지 않으므로,
실제 적용 시에는 반드시 공식 문서를 함께 참고해야 합니다.