클라이언트 JS에 관리자 비밀번호를 두면 안 되는 이유와 Firebase Auth UID 게이트
정적 사이트에 관리자 화면을 붙일 때 가장 쉬운 방법은 비밀번호 입력 창을 하나 만들고, 자바스크립트에서 입력값을 미리 적어 둔 문자열과 비교하는 것입니다. 이 사이트와 사진 갤러리도 처음에는 그렇게 만들었습니다. 결론부터 말하면 이 방법은 잠금이 아니라 장식입니다.
왜 안전하지 않은가
- 소스가 공개됩니다: 브라우저는 페이지를 보여 주기 위해 자바스크립트 파일을 통째로 내려받습니다. 개발자 도구나 "페이지 소스 보기"로 누구나 비교 문자열을 읽을 수 있습니다. 코드를 압축해도 문자열은 그대로 남습니다.
- 검사를 건너뛸 수 있습니다: 로그인 상태를 localStorage의 값 하나로 기억했다면, 콘솔에서 그 값을 직접 바꾸면 관리자 화면이 열립니다.
- 데이터는 잠기지 않습니다: 화면을 숨겨도 데이터베이스 쓰기 권한이 열려 있다면, 화면을 거치지 않고 API를 직접 호출해 데이터를 바꿀 수 있습니다. 진짜 잠금은 서버 쪽 규칙에 있어야 합니다.
- 비밀번호를 바꿔도 기록이 남습니다: git 저장소에 한 번 커밋된 비밀번호는 이후 커밋에서 지워도 이력에 남습니다. 다른 곳에서도 쓰던 비밀번호라면 그쪽도 바꿔야 합니다.
바꾼 구조: 로그인 + UID 비교 + 보안 규칙
- Firebase Authentication으로 로그인합니다. 이 사이트는 Google 로그인 팝업을 씁니다. 비밀번호 검증은 Google과 Firebase가 하고, 사이트 코드에는 비밀이 없습니다.
- 로그인한 사용자의 UID를 미리 정한 관리자 UID와 비교합니다. UID는 비밀이 아니라 계정 식별자이므로 코드에 적어도 됩니다. 다른 사람이 같은 UID로 로그인할 수는 없습니다.
- Firestore와 Storage 보안 규칙에서 같은 검사를 반복합니다. 규칙은 Google 서버에서 실행되므로, 화면을 우회해도 쓰기가 막힙니다.
// 보안 규칙 (서버에서 실행)
function isAdmin() {
return request.auth != null
&& request.auth.uid == "REPLACE_WITH_ADMIN_UID";
}
match /newsletters/{id} {
allow read: if true;
allow write: if isAdmin();
}
화면 쪽 코드는 onAuthStateChanged로 로그인 상태를 받아 UID가 일치할 때만 관리자 서랍을 엽니다. 이 검사는 사용자 경험을 위한 것이고, 실제 보호는 규칙이 합니다.
자리표시자 값으로 "닫힌 상태"를 기본값으로
관리자 UID는 처음에 REPLACE_WITH_ADMIN_UID 같은 자리표시자로 둡니다. 실제 UID로 바꾸기 전까지는 어떤 계정으로 로그인해도 일치하지 않으므로, 관리자 화면도 쓰기 권한도 모두 닫혀 있습니다. 실수로 설정을 빠뜨려도 열리는 쪽이 아니라 닫히는 쪽으로 실패하게 만드는 것이 핵심입니다.
관리자 진입 버튼 숨기기
방문자에게 열쇠 모양 버튼은 쓸모가 없습니다. 이 사이트는 버튼을 기본으로 숨기고, 관리자가 로그인한 상태이거나 주소에 ?admin을 붙였을 때만 보여 줍니다. 이것도 보안 수단은 아니고 화면 정리입니다. 버튼이 보여도 규칙이 데이터를 지킵니다.
설정 순서
- Firebase 콘솔 → Authentication → 로그인 방법에서 Google을 켭니다.
- 승인된 도메인에 실제 서비스 도메인을 추가합니다.
- 관리자 계정으로 한 번 로그인한 뒤 Users 목록에서 UID를 복사합니다.
- 코드의 ADMIN_UID와 규칙의 isAdmin()에 같은 값을 넣습니다.
- 규칙을 배포하기 전에, 같은 프로젝트를 쓰는 다른 앱의 규칙과 합쳐야 하는지 확인합니다. 규칙 파일은 프로젝트에 하나뿐이라 마지막에 배포한 것이 전체를 덮어씁니다. 합치는 방법은 Firestore 규칙 글에서 다룹니다.