Проверка запросов Places API с помощью Firebase AppCheck и reCAPTCHA

1. Прежде чем начать

Чтобы обеспечить легитимность пользователей, взаимодействующих с вашим веб-приложением, вы внедрите проверку приложений Firebase, используя токены reCAPTCHA JWT для проверки сеансов пользователей. Эта настройка позволит вам безопасно обрабатывать запросы клиентского приложения к Places API (новое) .

b40cfddb731786fa.png

Живая ссылка

Что вы построите.

Чтобы продемонстрировать это, вы создадите веб-приложение, которое отображает карту при загрузке. Он также незаметно сгенерирует токен reCAPTCHA с помощью Firebase SDK. Затем этот токен отправляется на ваш сервер Node.js, где Firebase проверяет его перед выполнением любых запросов к API-интерфейсу Places.

Если токен действителен, Firebase App Check будет хранить его до истечения срока его действия, устраняя необходимость создания нового токена для каждого клиентского запроса. Если токен недействителен, пользователю будет предложено еще раз пройти проверку reCAPTCHA, чтобы получить новый токен.

2. Предварительные условия

Для завершения этой лаборатории кода вам необходимо ознакомиться с приведенными ниже элементами. daea823b6bc38b67.png

Требуемые продукты Google Cloud

  • Google Cloud Firebase App Check : база данных для управления токенами
  • Google reCAPTCHA : создание и проверка токенов. Это инструмент, используемый для отличия людей от ботов на веб-сайтах. Он работает путем анализа поведения пользователя, атрибутов браузера и сетевой информации для создания оценки, показывающей вероятность того, что пользователь является ботом. Если оценка достаточно высока, пользователь считается человеком и никаких дальнейших действий не требуется. Если оценка низкая, может быть представлена ​​головоломка CAPTCHA для подтверждения личности пользователя. Этот подход менее навязчив, чем традиционные методы CAPTCHA, что делает работу пользователя более плавной.
  • (Необязательно) Google Cloud App Engine : среда развертывания.

Необходимые продукты платформы Google Карт

В этой лаборатории кода вы будете использовать следующие продукты платформы Google Maps:

Другие требования для этой Codelab

Для завершения этой Codelab вам потребуются следующие учетные записи, службы и инструменты:

  • Аккаунт Google Cloud Platform с включенной оплатой.
  • Ключ API платформы Google Карт с включенными Maps JavaScript API и Places.
  • Базовые знания JavaScript, HTML и CSS.
  • Базовые знания Node.js.
  • Текстовый редактор или IDE по вашему выбору.

3. Настройтесь

Настройте платформу Google Карт

Если у вас еще нет учетной записи Google Cloud Platform и проекта с включенной оплатой, ознакомьтесь с руководством по началу работы с платформой Google Maps, чтобы создать платежную учетную запись и проект.

  1. В Cloud Console щелкните раскрывающееся меню проекта и выберите проект, который вы хотите использовать для этой лаборатории кода.

e7ffad81d93745cd.png

  1. Включите API и SDK платформы Google Maps, необходимые для этой лаборатории кода, в Google Cloud Marketplace . Для этого выполните действия, описанные в этом видео или этой документации .
  2. Создайте ключ API на странице «Учетные данные» Cloud Console. Вы можете выполнить действия, описанные в этом видео или в этой документации . Для всех запросов к платформе Google Maps требуется ключ API.

Учетные данные приложения по умолчанию

Вы будете использовать Firebase Admin SDK для взаимодействия с вашим проектом Firebase, а также для отправки запросов к Places API, и для его работы вам потребуется предоставить действительные учетные данные.

Мы будем использовать аутентификацию ADC (автоматические учетные данные по умолчанию) для аутентификации вашего сервера для выполнения запросов. В качестве альтернативы (не рекомендуется) вы можете создать учетную запись службы и хранить учетные данные в своем коде.

Определение . Учетные данные приложения по умолчанию (ADC) — это механизм, который Google Cloud предоставляет для автоматической аутентификации ваших приложений без явного управления учетными данными. Он ищет учетные данные в различных местах (например, в переменных среды, файлах сервисных учетных записей или на сервере метаданных Google Cloud) и использует первое найденное.

  • В своем терминале используйте приведенную ниже команду, которая позволит вашим приложениям безопасно получать доступ к ресурсам Google Cloud от имени текущего вошедшего в систему пользователя:
gcloud auth application-default login
  • Вы создадите файл .env в корне, в котором будет указана переменная проекта Google Cloud:
GOOGLE_CLOUD_PROJECT="your-project-id"

Создать учетную запись службы

  • Вкладка «Платформа Google Карт» > «+Создать учетные данные» > Сервисный аккаунт.
  • Добавьте роль администратора Firebase AppCheck, затем введите только что введенное имя учетной записи службы, например: firebase-appcheck-codelab@yourproject.iam.gserviceaccount.com.

Реквизиты для входа

  • Нажмите на созданный сервисный аккаунт.
  • Перейдите на вкладку КЛЮЧИ, чтобы создать ключ > JSON > сохранить загруженные учетные данные json. Переместите автоматически загруженный файл xxx.json в корневую папку.
  • (Следующая глава) Правильно назовите его в файле nodejs server.js (firebase-credentials.json).

4. Интеграция Firebase AppCheck

Вы получите сведения о конфигурации Firebase и секретные ключи reCAPTCHA.

Вы вставите их в демо-приложение и запустите сервер.

Создайте приложение в Firebase

ВЫБЕРИТЕ уже созданный проект Google Cloud (возможно, вам придется указать: «Выбор родительского ресурса»)»

a6d171c6d7e98087.pnga16010ba102cc90b.png

  • Добавьте приложение из верхнего левого меню (шестеренка)

18e5a7993ad9ea53.png4632158304652118.png

Код инициализации Firebase

  • Сохраните код инициализации Firebase , чтобы вставить его в script.js (следующая глава) для клиентской стороны.

f10dcf6f5027e9f0.png

  • Зарегистрируйте свое приложение, чтобы разрешить Firebase использовать токены reCAPTCHA v3.

https://console.firebase.google.com/u/0/project/ВАШ_ПРОЕКТ/appcheck/apps

da7efe203ce4142c.png

  • Выберите reCAPTCHA → создайте ключ на веб-сайте reCAPTCHA (с правильными настройками доменов: localhost для разработки приложений).

b47eab131617467.pnge6bddef9d5cf5460.png

  • Вставьте секрет reCAPTCHA в Firebase AppCheck.

а63bbd533a1b5437.png

  • Статус приложения должен стать зеленым

4f7962b527b78ee5.png

5. Демо-приложение

  • Клиентское веб-приложение: файлы HTML, JavaScript, CSS.
  • Сервер: файл Node.js.
  • Среда (.env): ключи API.
  • Конфигурация (app.yaml): настройки развертывания Google App Engine.

Настройка Node.js:

  • Навигация : откройте терминал и перейдите в корневой каталог клонированного проекта.
  • Установите Node.js (при необходимости) : версия 18 или выше.
node -v  # Check installed version
  • Инициализировать проект: выполните следующую команду, чтобы инициализировать новый проект Node.js, оставив все настройки по умолчанию:
npm init 
  • Установить зависимости: используйте следующую команду для установки необходимых зависимостей проекта:
npm install @googlemaps/places firebase-admin express axios dotenv

Конфигурация: переменные среды для проекта Google Cloud

  • Создание файла среды: в корневом каталоге вашего проекта создайте файл с именем .env . В этом файле будут храниться конфиденциальные данные конфигурации, и его не следует подвергать контролю версий.
  • Заполнение переменных среды. Откройте файл .env и добавьте следующие переменные, заменив заполнители фактическими значениями из вашего проекта 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. Обзор кода

index.html

  • Загружает библиотеки Firebase для создания токена в приложении.
<!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>

скрипт.js

  • Извлекает ключи API: извлекает ключи API для Google Maps и проверки приложений Firebase с внутреннего сервера.
  • Инициализирует Firebase: настраивает Firebase для аутентификации и безопасности. (Заменить конфигурацию → см. главу 4).

Срок действия токена проверки приложений Firebase (от 30 минут до 7 дней) настраивается в консоли Firebase и не может быть изменен путем принудительного обновления токена.

  • Активирует проверку приложений: включает проверку приложений Firebase для проверки подлинности входящих запросов.
  • Загружает API Карт Google: динамически загружает библиотеку JavaScript Google Карт для отображения карты.
  • Инициализирует карту: создает карту Google с центром в местоположении по умолчанию.
  • Обрабатывает щелчки по карте: прослушивает щелчки по карте и соответствующим образом обновляет центральную точку.
  • Запросы Places API: отправляет запросы к серверному API ( /api/data ) для получения информации о местах (ресторанах, парках, барах) рядом с выбранным местом, используя Firebase App Check для авторизации.
  • Отображает маркеры: отображает полученные данные на карте в виде маркеров, показывая их имена и значки.
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
...

сервер.js

  • Загружает переменные среды (ключи API, идентификатор проекта Google) из файла .env .
  • Запускает сервер, прослушивающий запросы на http://localhost:3000 .
  • Инициализирует Firebase Admin SDK, используя учетные данные приложения по умолчанию (ADC).
  • Получает токен reCAPTCHA от script.js .
  • Проверяет валидность полученного токена .
  • Если токен действителен, выполняет POST-запрос к API Google Адресов с включенными параметрами поиска.
  • Обрабатывает и возвращает клиенту ответ от Places API.
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. Запустите приложение

В выбранной вами среде запустите сервер с терминала и перейдите по адресу http://localhost:3000.

npm start 

Токен создается как глобальная переменная, скрытая от окна браузера пользователя, и передается на сервер для обработки. Подробности о токене можно найти в журналах сервера.

Подробную информацию о функциях сервера и ответе на запрос Places API Nearby Search можно найти в журналах сервера.

Поиск неисправностей:

Убедитесь, что идентификатор проекта Google соответствует настройкам:

  • в файле .env (переменная GOOGLE_CLOUD_PROJECT)
  • в конфигурации терминала gcloud:
gcloud config set project your-project-id
  • в настройке reCaptcha

e6bddef9d5cf5460.png

  • в настройке Firebase

7e17bfbcb8007763.png

Другой

  • Создайте токен отладки, который можно будет использовать вместо ключа сайта reCAPTCHA в script.js для целей тестирования и устранения неполадок.

9c0beb760d13faef.png

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);
}
  • Слишком много неудачных попыток аутентификации, например использование ложного ключа сайта рекапчи, может вызвать временное регулирование.
FirebaseError: AppCheck: Requests throttled due to 403 error. Attempts allowed again after 01d:00m:00s (appCheck/throttled).

Учетные данные АЦП

  • Убедитесь, что вы находитесь в правильной учетной записи gcloud.
gcloud auth login 
  • Убедитесь, что установлены необходимые библиотеки.
npm install @googlemaps/places firebase-admin
  • Убедитесь, что в server.js загружена библиотека Firebase.
const {GoogleAuth} = require('google-auth-library');
gcloud auth application-default login
  • Олицетворение: учетные данные ADC сохранены.
gcloud auth application-default login --impersonate-service-account your_project@appspot.gserviceaccount.com
  • В конечном итоге протестируйте ADC локально, сохранив следующий скрипт как test.js и запустив его в терминале: 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. Вот и все, Молодец!

Последующие шаги

Развертывание в App Engine:

  • Подготовьте свой проект к развертыванию в Google App Engine, внеся все необходимые изменения в конфигурацию.
  • Используйте инструмент командной строки gcloud или консоль App Engine для развертывания приложения.

Улучшите аутентификацию Firebase:

  • Токены по умолчанию и пользовательские токены: внедрите пользовательские токены Firebase для более глубокого использования сервисов Firebase.
  • Срок действия токена: установите соответствующий срок действия токена: короче для конфиденциальных операций (пользовательский токен Firebase до одного часа), дольше для общих сеансов (токен reCAPTCHA: от 30 минут до 7 часов).
  • Изучите альтернативы reCAPTCHA: выясните, подходят ли DeviceCheck (iOS), SafetyNet (Android) или App Attest для ваших нужд безопасности.

Интегрируйте продукты Firebase:

  • База данных реального времени или Firestore. Если вашему приложению требуется синхронизация данных в реальном времени или автономные возможности, интегрируйтесь с базой данных реального времени или Firestore.
  • Облачное хранилище: используйте облачное хранилище для хранения и обслуживания пользовательского контента, такого как изображения или видео.
  • Аутентификация: используйте аутентификацию Firebase для создания учетных записей пользователей, управления сеансами входа в систему и обработки сброса паролей.

Развернуть на мобильную версию:

  • Android и iOS. Если вы планируете использовать мобильное приложение, создайте версии для платформ Android и iOS.
  • SDK Firebase: используйте SDK Firebase для Android и iOS, чтобы легко интегрировать функции Firebase в ваши мобильные приложения.