Firebase Auth Context에서 인증 확인 상태와 계정 전환의 늦은 응답은 어떻게 다루나요?
Auth Context는 인증 상태를 화면에 공유하는 장치이며 서버 권한을 대신하지 않습니다. 인증 확인 중·로그인·비로그인 상태를 구분하고 계정이 바뀔 때 이전 사용자의 늦은 응답이 새 상태를 덮지 못하게 하세요.
적용 조건·범위: 사용자가 바꿀 수 있는 프로필 role을 근거로 서버 권한을 허용하면 안 됩니다. isAdmin은 claim 기반 UI 안내일 뿐이며 관리자 API는 ID token 검증 뒤 claim을 다시 검사하고 Firestore·Storage는 Rules를 적용합니다. banned 상태를 화면에서만 체크하는 것도 실제 차단이 아닙니다. 서버·Rules의 정책과 연결해야 합니다.
본문 근거: firebase.google.com/docs/auth/admin/custom-claims · firebase.google.com/docs/auth/web/manage-users · react.dev/reference/react/useEffect
학습 목표와 사전 지식
목표: Auth Context의 로딩·계정 변경·권한 표시 상태를 설계하고 라우팅과 권한 집행을 구분합니다.
사전 지식: React Context/useEffect, Next.js App Router, Firebase 초기화
AuthProvider는 인증을 구독해 화면에 공유합니다. 서버 접근 권한까지 보장하는 장치는 아닙니다. 인증과 claim 읽기가 끝나기 전에 보호 화면을 렌더링하지 않고, 이전 계정의 비동기 응답이 새 계정 상태를 덮어쓰지 않게 해야 합니다.
상단의 계정 전환 사고 실습은 Firebase 계정 없이 읽을 수 있습니다. 전체 Context를 실행할 때 필요한 auth 인스턴스는 Firebase 초기화 구조: Next.js firebase.ts 설계 기준에서, 로그인·로그아웃 호출은 Firebase Authentication 사용 기준: 로그인 유지와 비밀번호 재설정에서 준비합니다. 이전 TaskFlow 화면을 완성한 상태를 요구하지 않습니다.
먼저 풀어 볼 문제: 계정 전환 중 늦게 도착한 응답
독자 질문: A 계정의 관리자 claim 조회가 늦는 동안 로그아웃하고 B 계정으로 바뀌면 어떤 상태를 보여야 할까요?
- 초기:
user=null, loading=true, isAdmin=false. 첫 인증 결과가 아직 없으므로 보호 화면을 보여주지 않습니다. - A 로그인:
user=A, loading=true, isAdmin=false. A의 claim을 조회합니다. - A 로그아웃:
user=null, loading=false, isAdmin=false. 앞선 A 요청은 무효가 됩니다. - 뒤늦게 A의 관리자 claim이 도착: A의 결과를 버립니다. 화면의
isAdmin이 true로 되돌아가면 오류입니다. - B 로그인: B의 token 결과만 현재 상태에 반영합니다.
확인 질문: A 응답이 4단계에서 도착해도 왜 상태를 바꾸면 안 될까요? 아래 코드에서는 요청마다 generation을 증가시키고 응답 시 현재 번호와 비교합니다. 이 연습은 Firebase 연결 없이 상태 전이 원리를 이해하기 위한 것입니다. 실제 권한 집행은 서버의 ID token 검사와 Rules가 맡습니다.
전체 Context 모듈
Next.js 기존 프로젝트의 src/context/AuthProvider.tsx에 둡니다. src/shared/libs/firebase/firebase.ts는 초기화 글의 모듈을 사용합니다. 로그인·가입·로그아웃 동작은 Authentication 글의 유틸을 호출하고, Context는 현재 인증 상태와 권한 표시만 맡깁니다. 별도 프로필 쿼리 라이브러리는 이 최소 예제에 필요하지 않습니다.
src/context/AuthProvider.tsx · 전체 모듈; 기존 React/Next.js 프로젝트에 적용
"use client";
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
import { onIdTokenChanged, type User } from 'firebase/auth';
import { auth } from '../shared/libs/firebase/firebase';
type State = { user: User | null; loading: boolean; isAdmin: boolean; error: string | null };
const AuthContext = createContext<State | undefined>(undefined);
export function AuthProvider({ children }: { children: ReactNode }) {
const [state, setState] = useState<State>({ user: null, loading: true, isAdmin: false, error: null });
useEffect(() => {
let generation = 0;
const unsubscribe = onIdTokenChanged(auth, async (user) => {
const current = ++generation;
setState({ user, loading: !!user, isAdmin: false, error: null });
if (!user) return;
try {
const token = await user.getIdTokenResult();
if (current !== generation) return;
setState({ user, loading: false, isAdmin: token.claims.admin === true, error: null });
} catch {
if (current !== generation) return;
setState({ user, loading: false, isAdmin: false, error: '권한 정보를 확인하지 못했습니다.' });
}
});
return () => { generation += 1; unsubscribe(); };
}, []);
return <AuthContext.Provider value={state}>{children}</AuthContext.Provider>;
}
export function useAuth() {
const value = useContext(AuthContext);
if (!value) throw new Error('AuthProvider 내부에서 사용하세요.');
return value;
}

왜 요청 세대 번호를 비교하는가
A 계정의 claim 조회가 느린 동안 로그아웃하거나 B로 로그인할 수 있습니다. generation을 바꾸고 완료 시 동일한 번호일 때만 상태를 반영하면 이전 요청 결과를 버립니다. 로그아웃 시 isAdmin을 즉시 false로 만들고 구독 해제 때도 진행 중 요청을 무효화합니다. 이 처리는 네트워크 요청을 취소하는 것과 다릅니다.
라우팅은 보호 화면 안에서
루트 layout의 children을 AuthProvider로 감싸고 보호 화면에서 loading이면 로딩 UI를, !user면 로그인 안내 또는 router.replace("/auth/login")를 실행합니다. 리다이렉트는 effect에서 처리하고 결과가 정해지기 전에는 보호 데이터를 표시하지 않습니다. pathname.includes("/mypage")처럼 우연히 포함된 문자열까지 보호하지 말고 경로 세그먼트를 명시합니다. Context 전체에 라우팅 부작용을 넣으면 공개 페이지까지 얽히기 쉽습니다.

role 필드와 claim을 혼동하지 않기
사용자가 바꿀 수 있는 프로필 role을 근거로 서버 권한을 허용하면 안 됩니다. isAdmin은 claim 기반 UI 안내일 뿐이며 관리자 API는 ID token 검증 뒤 claim을 다시 검사하고 Firestore·Storage는 Rules를 적용합니다. banned 상태를 화면에서만 체크하는 것도 실제 차단이 아닙니다. 서버·Rules의 정책과 연결해야 합니다.
확인 순서
새로고침 시 첫 인증 결과 전 loading이 유지되는지 확인합니다. A→로그아웃→B 전환 중 네트워크를 느리게 만들어 A의 권한이 되살아나지 않는지 봅니다. claim 조회 실패 시 관리자 화면을 숨기고 에러를 표시해야 합니다. UI isAdmin을 임의 변경해도 실제 관리자 요청이 거부되는지 개발 서버에서 따로 검사합니다.
직접 확인할 결과
타입/비동기 세대 처리 정적 검토
Next.js 빌드·브라우저 계정 전환 미실행
검증 범위와 기준일
공식 문서 확인일: 2026-09-13. 웹 SDK 예제는 모듈형 API 기준입니다. 이 글의 코드·구조 정적 검토와 실제 클라우드 인증·저장·배포 검증을 구분합니다. 본인 개발 Firebase 프로젝트의 실제 인증·권한·배포 요청은 이 편집 환경에서 실행하지 않았습니다.
이어서 학습하기
공식 문서
이 글이 도움이 되었나요?
읽음 기록
읽음 — / 전체 20개 · 필수 14개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.