1. Zanim zaczniesz
Aby mieć pewność, że użytkownicy, którzy korzystają z Twojej aplikacji internetowej, są autentyczni, wprowadź Sprawdzanie aplikacji Firebase, korzystając z tokenów JWT reCAPTCHA do weryfikowania sesji użytkowników. Dzięki temu będziesz mógł bezpiecznie obsługiwać żądania wysyłane przez aplikację klienta do interfejsu Places API (New).
Co utworzysz.
Aby to zademonstrować, utwórz aplikację internetową, która po załadowaniu wyświetla mapę. Skryjesz też token reCAPTCHA za pomocą pakietu Firebase SDK. Następnie token jest wysyłany na serwer Node.js, gdzie Firebase weryfikuje go przed spełnieniem żądań do interfejsu Places API.
Jeśli token jest prawidłowy, Sprawdzanie aplikacji Firebase będzie go przechowywać do momentu wygaśnięcia, co eliminuje konieczność tworzenia nowego tokena dla każdego żądania klienta. Jeśli token jest nieprawidłowy, użytkownik zostanie poproszony o ponowne przejście weryfikacji reCAPTCHA w celu uzyskania nowego tokena.
2. Wymagania wstępne
Aby ukończyć to ćwiczenie, musisz zapoznać się z informacjami poniżej.
Wymagane usługi Google Cloud
- Sprawdzanie aplikacji Firebase w Google Cloud: baza danych do zarządzania tokenami
- Google reCAPTCHA: tworzenie i weryfikacja tokena. Jest to narzędzie służące do odróżniania ludzi od botów w witrynach. Polega ono na analizie zachowania użytkownika, atrybutów przeglądarki i informacji o sieci w celu wygenerowania wyniku wskazującego na prawdopodobieństwo, że użytkownik jest botem. Jeśli wynik jest wystarczająco wysoki, użytkownik jest uznawany za człowieka i nie musi podejmować żadnych dalszych działań. Jeśli wynik jest niski, może zostać wyświetlona łamigłówka CAPTCHA, która pozwoli potwierdzić tożsamość użytkownika. To rozwiązanie jest mniej uciążliwe niż tradycyjne metody CAPTCHA, co zwiększa wygodę użytkowników.
- (Opcjonalnie) Google Cloud App Engine: środowisko wdrażania.
Wymagane usługi Google Maps Platform
W tym Codelab użyjesz tych usług Google Maps Platform:
- Interfejs Maps JavaScript API wczytany i wyświetlany w aplikacji internetowej
- Places API (Nowy): żądanie wysłane przez serwer backendu
Inne wymagania dotyczące tego ćwiczenia z programowania
Aby ukończyć to ćwiczenie, potrzebujesz tych kont, usług i narzędzi:
- Konto Google Cloud Platform z włączonym rozliczaniem
- Klucz interfejsu API Google Maps Platform z włączonymi interfejsami Maps JavaScript API i Places
- podstawy JavaScriptu, HTML-a i CSS-a;
- podstawową znajomość Node.js;
- Edytor tekstu lub IDE
3. Konfiguracja
Konfigurowanie Google Maps Platform
Jeśli nie masz jeszcze konta Google Cloud Platform ani projektu z włączonymi płatnościami, zapoznaj się z poradnikiem Pierwsze kroki z Google Maps Platform, aby utworzyć konto rozliczeniowe i projekt.
- W konsoli Google Cloud kliknij menu projektu i wybierz projekt, którego chcesz użyć w tym CodeLab.
- Włącz interfejsy API i pakiety SDK Google Maps Platform wymagane w tym laboratorium kodu na platformie Google Cloud Marketplace. Aby to zrobić, wykonaj czynności opisane w tym filmie lub w tej dokumentacji.
- Wygeneruj klucz interfejsu API na stronie Dane logowania w konsoli Cloud. Możesz wykonać czynności opisane w tym filmie lub w tej dokumentacji. Wszystkie żądania do Google Maps Platform wymagają klucza interfejsu API.
Domyślne dane logowania aplikacji
Pakiet SDK Firebase Admin będzie Ci służyć do interakcji z projektem Firebase oraz do wysyłania żądań do interfejsu Places API. Aby działał, musisz podać prawidłowe dane logowania.
Do uwierzytelniania serwera w celu wysyłania żądań użyjemy uwierzytelniania ADC (automatycznego uwierzytelniania za pomocą domyślnych danych logowania). Możesz też (nie jest to zalecane) utworzyć konto usługi i przechowywać w nim dane uwierzytelniające.
Definicja: domyślne uwierzytelnianie aplikacji (ADC) to mechanizm Google Cloud umożliwiający automatyczne uwierzytelnianie aplikacji bez konieczności zarządzania danymi logowania. Szuka danych logowania w różnych lokalizacjach (np. w zmiennych środowiskowych, plikach konta usługi lub na serwerze metadanych Google Cloud) i używa pierwszego znalezionego.
- W terminalu użyj tego polecenia, które umożliwia Twoim aplikacjom bezpieczny dostęp do zasobów Google Cloud w imieniu aktualnie zalogowanego użytkownika:
gcloud auth application-default login
- Utwórz w katalogu głównym plik .env, który określa zmienną projektu Google Cloud:
GOOGLE_CLOUD_PROJECT="your-project-id"
Inne dane logowania (niezalecane)
Tworzenie konta usługi
- Karta Google Maps Platform > „+Utwórz dane logowania” > Konto usługi
- Dodaj rolę Administratora AppCheck Firebase, a potem wpisz nazwę konta usługi, którą właśnie wpisano, np. firebase-appcheck-codelab@yourproject.iam.gserviceaccount.com.
Dane logowania
- Kliknij utworzone konto usługi.
- Na karcie KLUCZE kliknij Utwórz klucz > JSON > zapisz pobrane dane logowania w formacie JSON. Przenieś automatycznie pobrany plik xxx.json do folderu głównego.
- (Następny rozdział) Wpisz poprawną nazwę w pliku nodejs server.js (firebase-credentials.json)
4. Integracja z Firebase AppCheck
Uzyskasz szczegóły konfiguracji Firebase i klucze tajne reCAPTCHA.
Wklej je w aplikacji demonstracyjnej i uruchom serwer.
Tworzenie aplikacji w Firebase
- Otwórz stronę Administracja projektu https://console.firebase.google.com (znajdź linki):
WYBIERZ projekt Google Cloud, który został już utworzony (może być konieczne określenie: „Wybieranie zasobu nadrzędnego”).
- Dodaj aplikację w lewym górnym rogu menu (ikona koła zębatego)
Kod inicjalizacji Firebase
- Zapisz kod inicjowania Firebase, aby wkleić go w pliku script.js (następny rozdział) po stronie klienta.
- Zarejestruj aplikację, aby umożliwić Firebase używanie tokenów reCAPTCHA v3
https://console.firebase.google.com/u/0/project/YOUR_PROJECT/appcheck/apps
- Wybierz reCAPTCHA → utwórz klucz na stronie reCAPTCHA (z prawidłowo skonfigurowanymi domenami: localhost dla programistów aplikacji).
- Wklej klucz tajny reCAPTCHA w Firebase AppCheck
- Stan aplikacji powinien się zmienić na zielony.
5. Aplikacja demonstracyjna
- Aplikacja internetowa klienta: pliki HTML, JavaScript i CSS.
- Serwer: plik Node.js.
- Środowisko (plik .env): klucze interfejsu API
- Konfiguracja (plik app.yaml): ustawienia wdrożenia Google App Engine.
Konfiguracja Node.js:
- Przechodzenie: otwórz terminal i przejdź do głównego katalogu sklonowanego projektu.
- Zainstaluj Node.js (w razie potrzeby): wersja 18 lub nowsza.
node -v # Check installed version
- Inicjowanie projektu: uruchom to polecenie, aby zainicjować nowy projekt Node.js, pozostawiając wszystkie ustawienia domyślne:
npm init
- Zainstaluj zależności: aby zainstalować wymagane zależności projektu, użyj tego polecenia:
npm install @googlemaps/places firebase-admin express axios dotenv
Konfiguracja: zmienne środowiskowe projektu Google Cloud
- Tworzenie pliku środowiska: w katalogu głównym projektu utwórz plik o nazwie
.env
. Ten plik będzie przechowywać poufne dane konfiguracji i nie powinien być uwzględniany w kontroli wersji. - Wypełnianie zmiennych środowiskowych: otwórz plik
.env
i dodaj te zmienne, zastępując puste miejsca rzeczywistymi wartościami z projektu Google Cloud:
# Google Cloud Project ID
GOOGLE_CLOUD_PROJECT="your-cloud-project-id"
# reCAPTCHA Keys (obtained in previous steps)
RECAPTCHA_SITE_KEY="your-recaptcha-site-key"
RECAPTCHA_SECRET_KEY="your-recaptcha-secret-key"
# Maps Platform API Keys (obtained in previous steps)
PLACES_API_KEY="your-places-api-key"
MAPS_API_KEY="your-maps-api-key"
6. Omówienie kodu
index.html
- Ładuje biblioteki Firebase, aby utworzyć token w aplikacji
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Places API with AppCheck</title>
<style></style> </head>
<body>
<div id="map"></div>
<!-- Firebase services -->
<script src="https://www.gstatic.com/firebasejs/9.15.0/firebase-app-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/9.15.0/firebase-app-check-compat.js"></script>
<script type="module" src="./script.js"></script>
<link rel="stylesheet" href="./style.css">
</body>
</html>
script.js
- Pobiera klucze interfejsu API: pobiera klucze interfejsu API Map Google i sprawdzającej aplikacji Firebase z serwera backendu.
- Inicjowanie Firebase: konfiguruje Firebase na potrzeby uwierzytelniania i zabezpieczeń. (Konfiguracja zastępcza – patrz rozdział 4).
Czas ważności tokena Sprawdzania aplikacji Firebase, który może wynosić od 30 minut do 7 dni, jest konfigurowany w konsoli Firebase i nie można go zmienić przez wymuszenie odświeżenia tokena.
- Aktywuje Sprawdzanie aplikacji: umożliwia Sprawdzaniu aplikacji Firebase weryfikowanie autentyczności przychodzących żądań.
- Wczytuje interfejs Google Maps API: dynamicznie wczytuje bibliotekę JavaScript Google Maps, aby wyświetlić mapę.
- Inicjowanie mapy: tworzy mapę Google wyśrodkowaną na domyślnej lokalizacji.
- Obsługuje kliknięcia na mapie: nasłuchuje kliknięć na mapie i odpowiednio aktualizuje punkt centralny.
- Wysyła zapytania do interfejsu Places API: wysyła żądania do interfejsu API (
/api/data
) w celu pobierania informacji o miejscach (restauracje, parki, bary) w pobliżu klikniętego miejsca, korzystając z autoryzacji Firebase App Check. - Wyświetlanie znaczników: pobrane dane są nanoszone na mapie jako znaczniki z nazwami i ikonami.
let mapsApiKey, recaptchaKey; // API keys
let currentAppCheckToken = null; // AppCheck token
async function init() {
try {
await fetchConfig(); // Load API keys from .env variable
/////////// REPLACE with your Firebase configuration details
const firebaseConfig = {
apiKey: "AIza.......",
authDomain: "places.......",
projectId: "places.......",
storageBucket: "places.......",
messagingSenderId: "17.......",
appId: "1:175.......",
measurementId: "G-CPQ.......",
};
/////////// REPLACE
// Initialize Firebase and App Check
await firebase.initializeApp(firebaseConfig);
await firebase.appCheck().activate(recaptchaKey);
// Get the initial App Check token
currentAppCheckToken = await firebase.appCheck().getToken();
// Load the Maps JavaScript API dynamically
const scriptMaps = document.createElement("script");
scriptMaps.src = `https://maps.googleapis.com/maps/api/js?key=${mapsApiKey}&libraries=marker,places&v=beta`;
scriptMaps.async = true;
scriptMaps.defer = true;
scriptMaps.onload = initMap; // Create the map after the script loads
document.head.appendChild(scriptMaps);
} catch (error) {
console.error("Firebase initialization error:", error);
// Handle the error appropriately (e.g., display an error message)
}
}
window.onload = init()
// Fetch configuration data from the backend API
async function fetchConfig() {
const url = "/api/config";
try {
const response = await fetch(url);
const config = await response.json();
mapsApiKey = config.mapsApiKey;
recaptchaKey = config.recaptchaKey;
} catch (error) {
console.error("Error fetching configuration:", error);
// Handle the error (e.g., show a user-friendly message)
}
}
// Initialize the map when the Maps API script loads
let map; // Dynamic Map
let center = { lat: 48.85557501, lng: 2.34565006 };
function initMap() {
map = new google.maps.Map(document.getElementById("map"), {
center: center,
zoom: 13,
mapId: "b93f5cef6674c1ff",
zoomControlOptions: {
position: google.maps.ControlPosition.RIGHT_TOP,
},
streetViewControl: false,
mapTypeControl: false,
clickableIcons: false,
fullscreenControlOptions: {
position: google.maps.ControlPosition.LEFT_TOP,
},
});
// Initialize the info window for markers
infoWindow = new google.maps.InfoWindow({});
// Add a click listener to the map
map.addListener("click", async (event) => {
try {
// Get a fresh App Check token on each click
const appCheckToken = await firebase.appCheck().getToken();
currentAppCheckToken = appCheckToken;
// Update the center for the Places API query
center.lat = event.latLng.lat();
center.lng = event.latLng.lng();
// Query for places with the new token and center
queryPlaces();
} catch (error) {
console.error("Error getting App Check token:", error);
}
});
}
function queryPlaces() {
const url = '/api/data'; // "http://localhost:3000/api/data"
const body = {
request: {
includedTypes: ['restaurant', 'park', 'bar'],
excludedTypes: [],
maxResultCount: 20,
locationRestriction: {
circle: {
center: {
latitude: center.lat,
longitude: center.lng,
},
radius: 4000,
},
},
},
};
// Provides token to the backend using header: X-Firebase-AppCheck
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Firebase-AppCheck': currentAppCheckToken.token,
},
body: JSON.stringify(body),
})
.then((response) => response.json())
.then((data) => {
// display if response successful
displayMarkers(data.places);
})
.catch((error) => {
alert('No places');
// eslint-disable-next-line no-console
console.error('Error:', error);
});
}
//// display places markers on map
...
server.js
- Ładuje zmienną środowiskową (klucze interfejsu API, identyfikator projektu Google) z pliku
.env
. - Uruchamia serwer i nasłuchuje żądań na porcie
http://localhost:3000
. - Inicjowanie pakietu Firebase Admin SDK za pomocą domyślnych danych logowania aplikacji.
- Otrzymuje token reCAPTCHA od
script.js
. - Weryfikuje ważność otrzymanego tokena.
- Jeśli token jest prawidłowy, wysyła żądanie POST do interfejsu Places API z dołączonymi parametrami wyszukiwania.
- Przetwarza i zwraca odpowiedź z Places API do klienta.
const express = require('express');
const axios = require('axios');
const admin = require('firebase-admin');
// .env variables
require('dotenv').config();
// Store sensitive API keys in environment variables
const recaptchaSite = process.env.RECAPTCHA_SITE_KEY;
const recaptchaSecret = process.env.RECAPTCHA_SECRET_KEY;
const placesApiKey = process.env.PLACES_API_KEY;
const mapsApiKey = process.env.MAPS_API_KEY;
// Verify environment variables loaded (only during development)
console.log('recaptchaSite:', recaptchaSite, '\n');
console.log('recaptchaSecret:', recaptchaSecret, '\n');
const app = express();
app.use(express.json());
// Firebase Admin SDK setup with Application Default Credentials (ADC)
const { GoogleAuth } = require('google-auth-library');
admin.initializeApp({
// credential: admin.credential.applicationDefault(), // optional: explicit ADC
});
// Main API Endpoint
app.post('/api/data', async (req, res) => {
const appCheckToken = req.headers['x-firebase-appcheck'];
console.log("\n", "Token", "\n", "\n", appCheckToken, "\n")
try {
// Verify Firebase App Check token for security
const appCheckResult = await admin.appCheck().verifyToken(appCheckToken);
if (appCheckResult.appId) {
console.log('App Check verification successful!');
placesQuery(req, res);
} else {
console.error('App Check verification failed.');
res.status(403).json({ error: 'App Check verification failed.' });
}
} catch (error) {
console.error('Error verifying App Check token:', error);
res.status(500).json({ error: 'Error verifying App Check token.' });
}
});
// Function to query Google Places API
async function placesQuery(req, res) {
console.log('#################################');
console.log('\n', 'placesApiKey:', placesApiKey, '\n');
const queryObject = req.body.request;
console.log('\n','Request','\n','\n', queryObject, '\n')
const headers = {
'Content-Type': 'application/json',
'X-Goog-FieldMask': '*',
'X-Goog-Api-Key': placesApiKey,
'Referer': 'http://localhost:3000', // Update for production(ie.: req.hostname)
};
const myUrl = 'https://places.googleapis.com/v1/places:searchNearby';
try {
// Authenticate with ADC
const auth = new GoogleAuth();
const { credential } = await auth.getApplicationDefault();
const response = await axios.post(myUrl, queryObject, { headers, auth: credential });
console.log('############### SUCCESS','\n','\n','Response','\n','\n', );
const myBody = response.data;
myBody.places.forEach(place => {
console.log(place.displayName);
});
res.json(myBody); // Use res.json for JSON data
} catch (error) {
console.log('############### ERROR');
// console.error(error); // Log the detailed error for debugging
res.status(error.response.status).json(error.response.data); // Use res.json for errors too
}
}
// Configuration endpoint (send safe config data to the client)
app.get('/api/config', (req, res) => {
res.json({
mapsApiKey: process.env.MAPS_API_KEY,
recaptchaKey: process.env.RECAPTCHA_SITE_KEY,
});
});
// Serve static files
app.use(express.static('static'));
// Start the server
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server listening on port ${port}`, '\n');
});
7. Uruchamianie aplikacji
W wybranym środowisku uruchom serwer w terminalu i przejdź do adresu http://localhost:3000.
npm start
Token jest tworzony jako zmienna globalna, ukryty w oknie przeglądarki użytkownika i przekazywany na serwer do przetworzenia. Szczegóły tokena znajdziesz w dziennikach serwera. | Szczegółowe informacje o funkcjach serwera i odpowiedzi na żądanie wyszukiwania w pobliżu w interfejsie Places API znajdziesz w logach serwera. |
Rozwiązywanie problemów:
Upewnij się, że identyfikator projektu Google jest spójny w konfiguracji:
- w pliku .env (zmienna GOOGLE_CLOUD_PROJECT).
- w konfiguracji gcloud w terminalu:
gcloud config set project your-project-id
- w konfiguracji reCaptcha.
- w konfiguracji Firebase
Inne
- Utwórz token debugowania, który może być używany zamiast klucza witryny reCAPTCHA w
script.js
na potrzeby testowania i rozwiązywania problemów.
try {
// Initialize Firebase first
await firebase.initializeApp(firebaseConfig);
// Set the debug token
if (window.location.hostname === 'localhost') { // Only in development
await firebase.appCheck().activate(
'YOUR_DEBUG_FIREBASE_TOKEN', // Replace with the token from the console
true // Set to true to indicate it's a debug token
);
} else {
// Activate App Check
await firebase.appCheck().activate(recaptchaKey);
}
- Zbyt wiele nieudanych prób uwierzytelnienia, np. użycie nieprawidłowego klucza witryny reCAPTCHA, może spowodować tymczasowe ograniczenie.
FirebaseError: AppCheck: Requests throttled due to 403 error. Attempts allowed again after 01d:00m:00s (appCheck/throttled).
Dane logowania do ADC
- Sprawdź, czy używasz właściwego konta gcloud
gcloud auth login
- Upewnij się, że wymagane biblioteki są zainstalowane
npm install @googlemaps/places firebase-admin
- Upewnij się, że biblioteka Firebase server.js jest załadowana.
const {GoogleAuth} = require('google-auth-library');
- Lokalny proces programowania: konfigurowanie ADC
gcloud auth application-default login
- Podszycie się: dane logowania ADC zostały zapisane
gcloud auth application-default login --impersonate-service-account your_project@appspot.gserviceaccount.com
- Ostatecznie przetestuj lokalnie ADC, zapisując ten skrypt jako test.js i uruchamiając go w terminalu:
node test.js
const {GoogleAuth} = require('google-auth-library');
async function requestTestADC() {
try {
// Authenticate using Application Default Credentials (ADC)
const auth = new GoogleAuth();
const {credential} = await auth.getApplicationDefault();
// Check if the credential is successfully obtained
if (credential) {
console.log('Application Default Credentials (ADC) loaded successfully!');
console.log('Credential:', credential); // Log the credential object
} else {
console.error('Error: Could not load Application Default Credentials (ADC).');
}
// ... rest of your code ...
} catch (error) {
console.error('Error:', error);
}
}
requestTestADC();
8. To wszystko. Dobra robota.
Dalsze kroki
Wdrażanie w App Engine:
- Przygotuj projekt do wdrożenia w Google App Engine, wprowadzając niezbędne zmiany w konfiguracji.
- Aby wdrożyć aplikację, użyj narzędzia wiersza poleceń
gcloud
lub konsoli App Engine.
Ulepsz Uwierzytelnianie Firebase:
- Domyślne i niestandardowe tokeny: wdróż niestandardowe tokeny Firebase, aby móc lepiej korzystać z usług Firebase.
- Czas ważności tokena: ustaw odpowiedni czas ważności tokena, krótszy w przypadku operacji wrażliwych (niestandardowy token Firebase do 1 godziny), dłuższy w przypadku sesji ogólnych (token reCAPTCHA: 30 minut do 7 godzin).
- Poznaj alternatywy dla reCAPTCHA: sprawdź, czy DeviceCheck (iOS), SafetyNet (Android) lub App Attest są odpowiednie do Twoich potrzeb związanych z bezpieczeństwem.
Integracja usług Firebase:
- Baza danych czasu rzeczywistego lub Firestore: jeśli Twoja aplikacja potrzebuje synchronizacji danych w czasie rzeczywistym lub funkcji offline, zintegruj ją z Bazą danych czasu rzeczywistego lub Firestore.
- Cloud Storage: służy do przechowywania i wyświetlania treści wygenerowanych przez użytkowników, takich jak obrazy czy filmy.
- Uwierzytelnianie: korzystaj z usługi Uwierzytelnianie Firebase do tworzenia kont użytkowników, zarządzania sesjami logowania i resetowania haseł.
Rozwijanie na urządzeniach mobilnych:
- Android i iOS: jeśli planujesz utworzyć aplikację mobilną, utwórz wersje na platformy Android i iOS.
- Pakiety SDK Firebase: użyj pakietów SDK Firebase na Androida i iOS, aby bezproblemowo zintegrować funkcje Firebase z aplikacjami mobilnymi.