https://nomadcoders.co/nwitter
파이어베이스의 사용법을 익히고자 노마드코더의 ‘트위터 클론코딩’ 강의를 수강했다.(위 링크로 접속할 수 있다.)
Firebase란
Firebase는 구글이 소유하고 있는 모바일 애플리케이션 개발 플랫폼으로, 직접 구현하기에는 복잡하고 어려운 기능들을 사용하기 쉽게 제공해준다.
- Authentication API를 통하여 계정 기능을 쉽게 만들 수 있다.(구글, 깃허브 로그인)
- Cloud FireStore로 데이터베이스를 사용할 수 있다.
- Hosting으로 웹 사이트를 배포할 수 있다.
- Cloud Storage로 사용자가 파일을 클라우드에 업로드할 수 있다.
그 외에도 Crashlytics, Test Lab, Google Analytics 등 다양한 기능을 제공한다.
사이트에 기재된 바에 따르면 매월 5만명의 사용자까지 무료로 authentication할 수 있고, 1Gb의 데이터를 무료로 사용할 수 있다. 또한 하루에 2만번의 write, 5만번의 read, 5GB의 cloud storage, 2만번의 업로드, 5만번의 다운로드까지는 무료이다.
Firebase 설치
https://console.firebase.google.com
위 링크를 통해서 Firebase 프로젝트를 추가할 수 있다.
설치 명령어 → npm install firebase@10.1.0 (@10.1.0 은 버전으로, 생략 가능
또 웹사이트에서 진행하다보면 표시된 코드를 복사해서 프로젝 파일에 붙여넣으라고 한다. console firebaseConfig = { apiKey: ….}
나는 src 안에 firebase.ts를 만들어서 이 파일안에 붙여 넣었다. 그리고 아래와 같은 코드를 추가했다.
// Firebase 초기화
const app = initializeApp(firebaseConfig);
// Firebase의 인증(Authentication) 인스턴스 생성
export const auth = getAuth(app);
// Firebase의 스토리지(Storage) 인스턴스 생성
export const storage = getStorage(app);
// Firebase의 Firestore 데이터베이스(DB) 인스턴스 생성
export const db = getFirestore(app);
App.tsx에서는 아래와 같은 코드를 추가했다.
// 로딩 상태와 로딩 상태를 변경하는 useState 훅 사용
const [isLoading, setLoading] = useState(true);
// 초기화 함수 정의: auth의 인증 상태가 준비될 때까지 대기하고 로딩 상태를 비활성화
const init = async () => {
// auth.authStateReady()를 사용하여 인증 상태가 준비될 때까지 대기
await auth.authStateReady();
// 로딩 상태를 비활성화
setLoading(false);
};
// useEffect 훅을 사용하여 컴포넌트가 마운트될 때 초기화 함수 호출
useEffect(() => {
init();
}, []);
auth.authStateReady()
최초 인증 상태가 완료될 때 실행되는 Promise를 return한다.
→ Firebase가 쿠키와 토큰을 읽고 백엔드와 소통해서 로그인 여부를 확인하는 동안 기다리겠다는 뜻
promise가 확인됨 → 현재 사용자는 유효한 사용자(사용자가 로그아웃한 경우에는 null)
auth.currentUser → 현재 로그인한 사용자(또는 null)
auth.signOut() → 현재 사용자를 로그아웃(이것은 사용자의 ID 토큰을 자동으로 취소하지 않는다)
auth.onAuthStateChanged() → 사용자의 로그인 상태 변경에 대한 관찰자를 추가
계정 생성(createUserWithEmailAndPassword)
계정을 생성할때는 createUserWithEmailAndPassword를 사용한다.(파라미터는 auth, email, password)
create-account.tsx에서 아래와 같이 코드를 추가했다.
// Firebase의 파이어베이스 모듈에서 제공하는 계정 생성 함수를 사용하여
// 사용자의 입력 데이터를 기반으로 새로운 계정을 생성하는 함수
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
// 폼 제출 이벤트의 기본 동작을 방지
e.preventDefault();
// 에러 메시지 초기화
setError('');
// 로딩 중이거나 이름, 이메일, 비밀번호 중 하나라도 비어있으면 함수 종료
if (isLoading || name === '' || email === '' || password === '') return;
try {
// 로딩 상태를 활성화
setLoading(true);
// Firebase에서 제공하는 계정 생성 함수를 사용하여
// 이메일과 비밀번호로 새로운 사용자 계정을 생성
const credentials = await createUserWithEmailAndPassword(
auth,
email,
password
);
// 생성된 사용자의 프로필 업데이트
await updateProfile(credentials.user, {
displayName: name,
});
// 홈 페이지로 이동
navigate('/');
} catch (e) {
// FirebaseError인 경우에만 에러 메시지 설정
if (e instanceof FirebaseError) {
setError(e.message);
}
} finally {
// 로딩 상태를 비활성화
setLoading(false);
}
};
Protected Route
사용자가 로그인하지 않은 경우 특정 페이지에 접근하지 못하게 하기 위해서 Protected Route를 구현했다.
// react-router-dom에서 Navigate 컴포넌트 및 firebase 모듈에서 auth 가져오기
import { Navigate } from 'react-router-dom';
import { auth } from '../firebase';
// ProtectedRoute 컴포넌트 정의
export default function ProtectedRoute({
children,
}: {
children: React.ReactNode;
}) {
// 현재 인증된 사용자를 가져오기
const user = auth.currentUser;
// 콘솔에 현재 사용자 정보 출력 (개발 및 디버깅 용도)
console.log(user);
// 만약 사용자가 인증되지 않았다면 로그인 페이지로 이동
if (user === null) {
return <Navigate to="/login" />;
}
// 인증된 사용자인 경우 자식 컴포넌트 렌더링
return children;
}
Protected Routes를 통하여 로그인해있는 동안은 새로고침해도 로그인이 유지되고 로그아웃하면 /login에 자동으로 향하게 설정했다.
로그인(signInWithEmailAndPassword)
로그인을 할 때는 signInWithEmailAndPassword을 사용한다.(파라미터는 auth, email, password)
// 로그인 폼 제출 이벤트 핸들러 함수
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
// 폼 제출 이벤트의 기본 동작을 방지
e.preventDefault();
// 에러 메시지 초기화
setError('');
// 로딩 중이거나 이메일 또는 비밀번호가 비어있으면 함수 종료
if (isLoading || email === '' || password === '') return;
try {
// 로딩 상태를 활성화
setLoading(true);
// Firebase에서 제공하는 로그인 함수를 사용하여 이메일과 비밀번호로 로그인
await signInWithEmailAndPassword(auth, email, password);
// 로그인 성공 후 홈 페이지로 이동
navigate('/');
} catch (e) {
// FirebaseError가 발생한 경우에만 에러 메시지 설정
if (e instanceof FirebaseError) {
setError(e.message);
}
} finally {
// 로딩 상태를 비활성화
setLoading(false);
}
};
소셜 로그인
파이어베이스에서 깃허브 소셜 로그인을 활성화하는 방법
- 파이어베이스 콘솔로 이동
- ‘인증’ 섹션으로 이동한 후, ‘로그인 방법’ 탭을 클릭
- 'GitHub’을 선택하고, ‘활성화’ 버튼을 클릭
- 이제 GitHub에서 필요한 설정을 완료해야 한다.
GitHub에서는 다음과 같이 설정을 진행한다.
- GitHub에 로그인한 후, 오른쪽 상단의 프로필 사진을 클릭하고 'Settings’를 선택
- 좌측 메뉴에서 'Developer settings’를 클릭
- 'OAuth Apps’를 선택하고 'New OAuth App’를 클릭
- 애플리케이션 이름, 홈페이지 URL, 애플리케이션 설명을 입력합니다.
- 'Authorization callback URL’에는 파이어베이스에서 제공하는 리디렉션 URL을 입력. URL은 파이어베이스 콘솔의 GitHub 로그인 설정 페이지에서 찾을 수 있다.
- 'Register application’을 클릭하여 애플리케이션을 생성
- 이제 생성된 OAuth 애플리케이션의 'Client ID’와 'Client Secret’을 복사하여 파이어베이스 콘솔의 GitHub 설정에 붙여넣는다.
이렇게 하면 파이어베이스에서 깃허브 소셜 로그인을 활성화하는 설정이 완료된다.
Firebase에 트윗 추가하기(addDoc)
위에서 언급했듯이, Cloud Firestore를 통하여 데이터베이스를 사용할 수 있다.
밑 코드는 addDoc을 통하여 Firestore에 트윗을 추가하는 코드이다.
(firestore에 tweets 컬렉션을 추가한 상태이다.
→ firestore 웹페이지에서 컬렉션을 추가해주자.)
// Firestore에 트윗 추가
const doc = await addDoc(collection(db, 'tweets'), {
// 'tweets' 컬렉션에 새로운 문서를 추가하고, 해당 문서의 참조를 반환한다.
tweet, // 트윗 내용
createdAt: Date.now(), // 현재 시간을 기록하여 트윗이 생성된 시간을 저장한다.
username: user.displayName || 'Anonymous', // 사용자의 디스플레이 이름을 가져오거나, 없으면 'Anonymous'로 설정
userId: user.uid, // 사용자의 고유 식별자(ID)를 저장
});
Firebase에 이미지 업로드(uploadBytes, getDownloadURL, updateDoc)
uploadBytes, getDownloadURL, updateDoc을 통하여 파일을 업로드 할 수 있다.
// 파일이 존재하는 경우에만 실행
if (file) {
// 파일을 업로드할 Firebase Storage의 경로 설정
const locationRef = ref(storage, `tweets/${user.uid}/${doc.id}`);
// 파일 업로드를 위해 Storage에 바이트 업로드 진행
const result = await uploadBytes(locationRef, file);
// 업로드가 완료된 파일의 다운로드 URL 가져오기
const url = await getDownloadURL(result.ref);
// Firestore의 트윗 문서 업데이트 - photo 필드에 다운로드 URL 추가
await updateDoc(doc, {
photo: url,
});
// 트윗 내용 및 파일 상태 초기화
setTweet('');
setFile(null);
}
// 예외 처리: 에러가 발생한 경우 콘솔에 로그 출력
} catch (e) {
console.log(e);
} finally {
// 로딩 상태를 비활성화
setLoading(false);
}
타임라인 관리하기(query, unscribe, onSnapshot)
실시간으로 query, unscribe, onSnapshot을 통하여 타임라인을 관리할 수 있다.
1. query
query는 Firestore에서 데이터를 읽거나 쓸 때 사용하는 데이터베이스 쿼리를 나타내는 객체 Firestore의 query 함수는 쿼리를 생성하고 데이터를 검색할 때 사용된다.
Firestore 쿼리를 생성하는 query 함수는 다음과 같은 형태를 가집니다:
query(reference: CollectionReference | Query, ...constraints: QueryConstraint[]): Query;
- reference: 쿼리가 적용될 컬렉션 또는 이전 쿼리의 결과를 나타내는 CollectionReference 또는 Query
- constraints: 필터, 정렬, 제한 등의 추가적인 쿼리 제약(Constraints)을 지정하는 매개변수
query 함수를 사용하여 Firestore에서 데이터를 가져오기 위한 기본적인 쿼리를 생성할 수 있습니다. 이 쿼리는 주로 onSnapshot 또는 getDocs 함수와 함께 사용되어 데이터베이스에서 데이터를 읽어오거나, 실시간 업데이트를 구독할 때 활용된다.
예를 들어, 특정 컬렉션에서 생성 시간을 기준으로 내림차순으로 정렬된 최근 10개의 문서를 가져오는 쿼리는 다음과 같이 작성할 수 있다.
import { query, collection, orderBy, limit } from 'firebase/firestore';
const tweetsQuery = query(
collection(db, 'tweets'),
orderBy('createdAt', 'desc'),
limit(10)
);
위의 코드에서:
- collection(db, 'tweets'): 'tweets' 컬렉션을 나타내는 CollectionReference를 생성
- orderBy('createdAt', 'desc'): 'createdAt' 필드를 기준으로 내림차순으로 정렬
- limit(10): 결과를 최대 10개로 제한
이렇게 생성된 tweetsQuery는 Firestore에서 트윗 데이터를 가져오거나 구독할 때 사용할 수 있는 쿼리 객체가 된다.
2. onSnapshot
onSnapshot은 Firestore에서 제공하는 함수로, 쿼리 결과에 대한 실시간 업데이트를 수신할 수 있게 한다. 쿼리 결과에 변화가 있을 때마다 해당 함수가 실행되며, 업데이트된 데이터를 처리할 수 있다.
예를 들어:
const query = query(collection(db, 'example'));
const unsubscribe = onSnapshot(query, (snapshot) => {
snapshot.docChanges().forEach((change) => {
// 업데이트된 데이터 처리 로직
});
});
이벤트 리스너로 등록된 콜백 함수는 데이터에 변경이 있을 때 호출된다. onSnapShot을 통해 등록된 콜백은 해당 데이터의 추가, 수정, 삭제 등의 변경사항에 반응하여 동작한다.
3. unscribe
unsubscribe는 Firestore에서 데이터베이스에서 실시간 업데이트를 수신하기 위해 사용되는 구독자(subscriber)를 해제하는 데에 사용된다. 이것은 일반적으로 onSnapshot 함수에서 반환되는 구독자 객체를 나타냅니다. 구독자를 정리함으로써, 컴포넌트가 더 이상 필요하지 않을 때 불필요한 업데이트를 방지할 수 있다.
예를 들어:
useEffect(() => {
let unsubscribe: Unsubscribe | null = null;
const fetchData = () => {
const query = query(collection(db, 'example'));
unsubscribe = onSnapshot(query, (snapshot) => {
// 데이터 업데이트 로직
});
};
fetchData();
return () => {
unsubscribe && unsubscribe(); // 컴포넌트 언마운트 시 구독자 해제
};
}, []);
나는 Timeline.tsx에 아래와 같은 코드를 작성했다.
export default function Timeline() {
// 트윗 목록을 상태로 관리
const [tweets, setTweet] = useState<ITweet[]>([]);
// useEffect를 사용하여 컴포넌트가 마운트되면 트윗 데이터를 가져오고 실시간 업데이트를 수신
useEffect(() => {
// Firestore에서 실시간으로 트윗 데이터를 가져오기 위한 구독자 생성
let unsubscribe: Unsubscribe | null = null;
// 트윗 데이터를 가져오는 비동기 함수 정의
const fetchTweets = async () => {
// Firestore의 'tweets' 컬렉션에서 트윗을 가져오는 쿼리 생성
const tweetsQuery = query(
collection(db, 'tweets'),
orderBy('createdAt', 'desc'), // 생성 시간 기준으로 내림차순 정렬
limit(25) // 최근 25개의 트윗만 가져오도록 제한
);
// onSnapshot을 사용하여 실시간 업데이트를 수신하고 상태 업데이트
unsubscribe = onSnapshot(tweetsQuery, (snapshot) => {
// 가져온 문서를 매핑하여 필요한 데이터 추출 및 구조화
const tweets = snapshot.docs.map((doc) => {
const { tweet, createdAt, userId, username, photo } = doc.data();
return {
tweet,
createdAt,
userId,
username,
photo,
id: doc.id,
};
});
// 상태 업데이트를 통해 트윗 목록 갱신
setTweet(tweets);
});
};
// 컴포넌트가 마운트될 때 한 번만 실행되는 초기 데이터 로드 함수 호출
fetchTweets();
// 컴포넌트 언마운트 시, 구독자를 정리하여 불필요한 업데이트 방지
return () => {
unsubscribe && unsubscribe();
};
}, []); // 빈 배열을 의존성으로 전달하여 한 번만 실행되도록 설정
}
트윗 삭제하기(deleteDoc, deleteObject)
deleteDoc과 deleteObject를 활용하여 트윗을 삭제할 수 있다.
1. deleteDoc:
deleteDoc(doc: DocumentReference): Promise<void>;
- doc: 삭제할 Firestore 문서를 나타내는 DocumentReference 객체
deleteDoc 함수는 주어진 문서 참조(DocumentReference)에 해당하는 Firestore 문서를 비동기적으로 삭제한다. 삭제 작업이 성공적으로 완료되면 Promise는 완료된다.
예를 들어, 아래의 코드에서는 'tweets' 컬렉션 내의 특정 문서를 삭제한다.
await deleteDoc(doc(db, 'tweets', 'documentId'));
2. deleteObject:
deleteObject(ref: Reference): Promise<void>;
- ref: 삭제할 Firebase Storage 객체(파일)를 나타내는 Reference 객체
deleteObject 함수는 Firebase Storage에서 주어진 참조(Reference)에 해당하는 객체(파일)를 비동기적으로 삭제한. 삭제 작업이 성공적으로 완료되면 Promise는 완료된다.
아래의 코드에서는 'tweets' 경로 내의 특정 파일을 삭제한다.
const storageRef = ref(storage, 'tweets/userId/documentId');
await deleteObject(storageRef);
나는 삭제하는 함수를 아래와 같이 구현했다.
// 트윗 삭제 함수 정의
const onDelete = async () => {
// 사용자에게 삭제 확인 메시지 띄우기
const ok = confirm('Are you sure you want to delete this tweet?');
// 사용자가 확인을 누르지 않았거나, 현재 사용자가 트윗 작성자가 아니면 함수 종료
if (!ok || user?.uid !== userId) return;
try {
// Firestore에서 트윗 문서 삭제
await deleteDoc(doc(db, 'tweets', id));
// 트윗에 사진이 있는 경우, Storage에서 사진 삭제
if (photo) {
const photoRef = ref(storage, `tweets/${user.uid}/${id}`);
await deleteObject(photoRef);
}
} catch (e) {
// 에러가 발생한 경우 콘솔에 로그 출력
console.log(e);
} finally {
// 삭제 완료 후 필요한 로직이 있을 경우 여기에 추가
}
};
프로필에서 사용자의 트윗 가져오기(getDocs, snapshot)
getDocs과 snapshot을 활용하여 프로필에서 해당 사용자의 트윗을 가져왔다.
그리고 **doc.data()**는 Firestore 문서에서 데이터를 추출하는 메서드다. 이 메서드는 해당 문서에 저장된 모든 필드 및 값들을 가져와서 JavaScript 객체로 반환한다.
코드에서 사용된 구문은 비구조화 할당(Destructuring Assignment)으로, **doc.data()**에서 반환된 객체의 속성을 개별 변수로 추출하는 것이다. 각 변수는 해당 문서 필드의 값을 나타낸다.
나는 아래와 같은 코드를 작성했다.
// 특정 사용자의 트윗을 가져오는 함수 정의
const fetchTweets = async () => {
// Firestore에서 해당 사용자의 트윗을 가져오기 위한 쿼리 생성
const tweetQuery = query(
collection(db, 'tweets'),
where('userId', '==', user?.uid), // userId가 현재 사용자의 uid와 일치하는 조건 추가
orderBy('createdAt', 'desc'), // 생성 시간을 기준으로 내림차순 정렬
limit(25) // 최근 25개의 트윗만 가져오도록 제한
);
// 쿼리 실행하여 결과 스냅샷을 얻음
const snapshot = await getDocs(tweetQuery);
// 각각의 문서를 매핑하여 트윗 객체로 구성
const tweets = snapshot.docs.map((doc) => {
const { tweet, createdAt, userId, username, photo } = doc.data();
return {
tweet,
createdAt,
userId,
username,
photo,
id: doc.id,
};
});
// 트윗 목록을 상태로 업데이트
setTweets(tweets);
};
// 컴포넌트가 마운트되면 트윗을 가져오는 함수 호출
useEffect(() => {
fetchTweets();
}, []);
Firebase 호스팅하기
firebase 홈페이지서 Build → Hosting으로 가서 차례차례 따라하자.
(참고로 나는 node.js 16.20.2를 쓰고 있었는데
‘Firebase CLI v13.1.0 is incompatible with Node.js v16.20.2 Please upgrade Node.js to version >=18.0.0 || >=20.0.0’
라고 오류가 떠서 Node.js를 업그레이드 해줘야 했다.)
(강의에서는 vite를 사용하고 있었고 vite가 모든 것을 압축해 저장하는 폴더의 이름이 ‘dist’였으므로 init 설정을 할 때 dist를 public 폴더로 설정했다.)
package.json에서 아래와 같이 코드를 추가했다.
"scripts": {
(중략)
"predeploy": "npm run build",
"deploy" : "firebase deploy",
},
그리고 npm run deploy 명령어를 실행해서 배포를 했다.
npm run deploy 명령어를 실행하면, 먼저 "predeploy" 스크립트에 정의된 **npm run build**가 실행되어 프로젝트를 빌드하고, 그 다음에 "deploy" 스크립트에 정의된 **firebase deploy**가 실행되어 Firebase에 프로젝트를 배포한다. 이렇게 스크립트를 설정하면 배포 전에 필요한 빌드 작업 등을 자동으로 수행할 수 있다.
Security Rules
어떻게 해도 API key는 공개될 수 밖에 없다.
FireStore의 규칙에서 코드를 수정해서 규칙을 주자.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /tweets/{doc} {
allow read, create: if request.auth != null
allow delete, update: if request.auth.uid == resource.data.userId
}
}
}
Firebase의 보안 규칙은 Firestore 데이터베이스에 대한 접근 권한을 정의하는 규칙이다.
- rules_version = '2';: Firebase 보안 규칙 버전을 나타내며, 이 경우에는 2번 버전을 사용하고 있습니다.
- service cloud.firestore { ... }: Firestore 데이터베이스에 대한 보안 규칙을 정의하는 부분입니다.
- match /databases/{database}/documents { ... }: Firestore 데이터베이스의 모든 문서에 대한 규칙을 적용합니다. {database}는 데이터베이스의 이름을 나타냅니다.
- match /tweets/{doc} { ... }: tweets 컬렉션의 모든 문서에 대한 규칙을 정의합니다. {doc}는 해당 문서의 ID를 나타냅니다.
- allow read, create: if request.auth != null: 읽기 및 쓰기 작업은 인증된 사용자에게 허용됩니다. request.auth != null은 사용자가 인증되었는지 확인하는 조건입니다.
- allow delete, update: if request.auth.uid == resource.data.userId: 삭제와 업데이트 작업은 해당 문서의 userId 필드가 현재 인증된 사용자의 UID와 일치하는 경우에만 허용됩니다. 이는 해당 문서를 생성한 사용자만이 해당 문서를 삭제하거나 업데이트할 수 있도록 하는 규칙입니다.
따라서 이 보안 규칙은 사용자가 특정 문서를 읽고 쓰는 것은 인증된 사용자에게만 허용하며, 삭제 및 업데이트는 해당 문서를 만든 사용자에게만 허용합니다.
storage에서는
rules_version = '2';
// Craft rules based on data in your Firestore database
// allow write: if firestore.get(
// /databases/(default)/documents/users/$(request.auth.uid)).data.isAdmin;
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read: if request.auth != null
allow write: if request.auth != null && resource.size < 2 * 1024 * 1024
}
}
}
해당 Firebase Storage 보안 규칙은 Firestore 데이터베이스에 대한 접근 권한을 확인하고, 특히 Firebase Storage에서의 파일 읽기 및 쓰기 작업에 대한 규칙을 정의합니다. 다음은 해당 규칙을 해석한 내용입니다:
- rules_version = '2';: Firebase 보안 규칙의 버전을 나타내며, 이 경우에는 2번 버전을 사용하고 있습니다.
- service firebase.storage { ... }: Firebase Storage에 대한 규칙을 정의하는 부분입니다.
- match /b/{bucket}/o { ... }: Firebase Storage의 모든 버킷에 대한 규칙을 정의합니다. {bucket}은 해당 버킷의 이름을 나타냅니다.
- match /{allPaths=**} { ... }: 모든 파일에 대한 규칙을 정의합니다. {allPaths=**}는 모든 경로를 나타냅니다.
- allow read: if request.auth != null: 파일 읽기 작업은 인증된 사용자에게 허용됩니다. request.auth != null은 사용자가 인증되었는지 확인하는 조건입니다.
- allow write: if request.auth != null && resource.size < 2 * 1024 * 1024: 파일 쓰기 작업은 인증된 사용자에게 허용되며, 동시에 업로드하려는 파일의 크기가 2 MB 미만인 경우에만 허용됩니다. request.auth != null은 사용자가 인증되었는지 확인하고, resource.size는 업로드하려는 파일의 크기를 나타냅니다.
따라서 이 보안 규칙은 Firebase Storage에서의 파일 읽기는 모든 인증된 사용자에게, 파일 쓰기는 인증된 사용자이면서 파일 크기가 2 MB 미만인 경우에만 허용합니다.
API Key Security
console.cloud.google.com/apis/credentials로 이동
애플리케이션 제한사항 설정 → 웹사이트 → 웹사이트 제한사항 ADD → 본인 프로젝트 웹 사이트 주소 입력 (https:// 지우고)
이제 localhost에서 로그인을 시도하면 Firebase: Error (auth/requests-from-referer-http://localhost:5173-are-blocked.) 라고 에러가 뜬다.
→ 아까 입력한 웹사이트에서만 접속이 가능하다!
'Project > Nomadcoder' 카테고리의 다른 글
[프로젝트] 영화 정보 웹사이트 React로 구현하기 (1) | 2023.08.30 |
---|