1. Avant de commencer
Pour vous assurer de la légitimité des utilisateurs qui interagissent avec votre application Web, vous allez implémenter Firebase App Check, en utilisant des jetons JWT reCAPTCHA pour valider les sessions utilisateur. Cette configuration vous permettra de gérer de manière sécurisée les requêtes de l'application cliente vers l'API Places (nouvelle).
Ce que vous allez créer
Pour illustrer cela, vous allez créer une application Web qui affiche une carte au chargement. Il génère également un jeton reCAPTCHA de manière discrète à l'aide du SDK Firebase. Ce jeton est ensuite envoyé à votre serveur Node.js, où Firebase le valide avant de répondre aux requêtes envoyées à l'API Places.
Si le jeton est valide, Firebase App Check le stocke jusqu'à son expiration, ce qui élimine la nécessité de créer un nouveau jeton pour chaque requête client. Si le jeton n'est pas valide, l'utilisateur est invité à effectuer à nouveau la validation reCAPTCHA pour obtenir un nouveau jeton.
2. Prérequis
Vous devez vous familiariser avec les éléments ci-dessous pour suivre cet atelier de programmation.
Produits Google Cloud requis
- Google Cloud Firebase App Check: base de données pour la gestion des jetons
- Google reCAPTCHA: création et validation de jetons. Il s'agit d'un outil permettant de distinguer les humains des robots sur les sites Web. Il analyse le comportement des utilisateurs, les attributs du navigateur et les informations réseau pour générer un score indiquant la probabilité que l'utilisateur soit un robot. Si le score est suffisamment élevé, l'utilisateur est considéré comme humain et aucune autre action n'est requise. Si le score est faible, un puzzle CAPTCHA peut être présenté pour confirmer l'identité de l'utilisateur. Cette approche est moins intrusive que les méthodes CAPTCHA traditionnelles, ce qui améliore l'expérience utilisateur.
- (Facultatif) Google Cloud App Engine: environnement de déploiement.
Produits Google Maps Platform requis
Dans cet atelier de programmation, vous utiliserez les produits Google Maps Platform suivants :
- API Maps JavaScript chargée et affichée dans l'application Web
- Requête API Places (nouvelle version) émise par le serveur backend
Autres conditions requises pour cet atelier de programmation
Pour suivre cet atelier de programmation, vous aurez besoin des comptes, des services et des outils suivants:
- Compte Google Cloud Platform pour lequel la facturation est activée
- Une clé API Google Maps Platform pour laquelle l'API Maps JavaScript et Places sont activées
- Connaissances de base sur JavaScript, HTML et CSS
- Connaissances de base de Node.js
- Éditeur de texte ou IDE de votre choix
3. Préparer l'atelier
Configurer Google Maps Platform
Si vous ne disposez pas déjà d'un compte Google Cloud Platform et d'un projet sur lequel la facturation est activée, consultez le guide Premiers pas avec Google Maps Platform pour savoir comment créer un compte de facturation et un projet.
- Dans Cloud Console, cliquez sur le menu déroulant des projets, puis sélectionnez celui que vous souhaitez utiliser pour cet atelier de programmation.
- Activez les API et les SDK Google Maps Platform requis pour cet atelier de programmation depuis Google Cloud Marketplace. Pour ce faire, suivez les étapes indiquées dans cette vidéo ou cette documentation.
- Générez une clé API sur la page Identifiants de Cloud Console. Vous pouvez suivre la procédure décrite dans cette vidéo ou cette documentation. Toutes les requêtes envoyées à Google Maps Platform nécessitent une clé API.
Identifiants par défaut de l'application
Vous utiliserez le SDK Admin Firebase pour interagir avec votre projet Firebase et envoyer des requêtes à l'API Places. Vous devrez fournir des identifiants valides pour que cela fonctionne.
Nous utiliserons l'authentification ADC (Automatic Default Credentials) pour authentifier votre serveur afin qu'il puisse envoyer des requêtes. Vous pouvez également (non recommandé) créer un compte de service et stocker les identifiants dans votre code.
Définition: Les identifiants par défaut de l'application (ADC) sont un mécanisme fourni par Google Cloud pour authentifier automatiquement vos applications sans gérer explicitement les identifiants. Il recherche des identifiants dans différents emplacements (comme les variables d'environnement, les fichiers de compte de service ou le serveur de métadonnées Google Cloud) et utilise le premier qu'il trouve.
- Dans votre terminal, utilisez la commande ci-dessous, qui permet à vos applications d'accéder de manière sécurisée aux ressources Google Cloud au nom de l'utilisateur actuellement connecté:
gcloud auth application-default login
- Vous allez créer un fichier .env à la racine qui spécifie une variable de projet Google Cloud:
GOOGLE_CLOUD_PROJECT="your-project-id"
Autres identifiants (non recommandé)
Créer un compte de service
- Onglet Google Maps Platform > "+ Créer des identifiants" > Compte de service
- Ajoutez le rôle Administrateur Firebase AppCheck, puis saisissez le nom du compte de service que vous venez de saisir, par exemple: firebase-appcheck-codelab@yourproject.iam.gserviceaccount.com.
Identifiants
- Cliquez sur le compte de service créé.
- Dans l'onglet "CLÉS", cliquez sur "Créer une clé" > "JSON" > enregistrez les identifiants JSON téléchargés. Déplacez le fichier xxx.json téléchargé automatiquement dans votre dossier racine.
- (Chapitre suivant) Nommez-le correctement dans le fichier nodejs server.js (firebase-credentials.json)
4. Intégration de Firebase App Check
Vous obtiendrez les détails de configuration Firebase et les clés secrètes reCAPTCHA.
Vous les collerez dans l'application de démonstration et démarrerez le serveur.
Créer une application dans Firebase
- Accédez à la page Administration du projet https://console.firebase.google.com (recherchez les liens):
SELECT le projet Google Cloud déjà créé (vous devrez peut-être spécifier "Sélectionner la ressource parente")"
- Ajouter une application depuis le menu en haut à gauche (engrenage)
Code d'initialisation Firebase
- Enregistrez le code d'initialisation de Firebase pour le coller dans script.js (chapitre suivant) côté client.
- Enregistrer votre application pour autoriser Firebase à utiliser les jetons reCAPTCHA v3
https://console.firebase.google.com/u/0/project/YOUR_PROJECT/appcheck/apps
- Choisissez reCAPTCHA → créez une clé sur le site Web reCAPTCHA (avec les domaines appropriés configurés: localhost pour le développement d'applications)
- Coller la clé secrète reCAPTCHA dans Firebase AppCheck
- L'état de l'application doit passer au vert.
5. Application de démonstration
- Application Web cliente:fichiers HTML, JavaScript et CSS
- Serveur:fichier Node.js
- Environnement (.env) : clés API
- Configuration (app.yaml) : paramètres de déploiement Google App Engine
Configuration de Node.js:
- Parcourir: ouvrez votre terminal et accédez au répertoire racine de votre projet cloné.
- Installez Node.js (si nécessaire): version 18 ou ultérieure.
node -v # Check installed version
- Initialiser le projet:exécutez la commande suivante pour initialiser un projet Node.js, en laissant tous les paramètres par défaut:
npm init
- Installer les dépendances:utilisez la commande suivante pour installer les dépendances de projet requises:
npm install @googlemaps/places firebase-admin express axios dotenv
Configuration: Variables d'environnement pour le projet Google Cloud
- Création d'un fichier d'environnement:dans le répertoire racine de votre projet, créez un fichier nommé
.env
. Ce fichier stocke des données de configuration sensibles et ne doit pas être ajouté au contrôle des versions. - Répondez aux variables d'environnement:ouvrez le fichier
.env
et ajoutez les variables suivantes, en remplaçant les espaces réservés par les valeurs réelles de votre projet 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. Présentation du code
index.html
- Charge les bibliothèques Firebase pour créer le jeton dans l'application
<!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
- Récupère les clés API:récupère les clés API pour Google Maps et Firebase App Check à partir d'un serveur backend.
- Initialise Firebase:configure Firebase pour l'authentification et la sécurité. (Remplacer la configuration → voir le chapitre 4).
La durée de validité du jeton Firebase App Check, qui varie de 30 minutes à sept jours, est configurée dans la console Firebase et ne peut pas être modifiée en essayant de forcer un actualisation du jeton.
- Active App Check:permet à Firebase App Check de vérifier l'authenticité des requêtes entrantes.
- Charge l'API Google Maps:charge de manière dynamique la bibliothèque JavaScript Google Maps pour afficher la carte.
- Initialise la carte:crée une carte Google Maps centrée sur un emplacement par défaut.
- Gère les clics sur la carte:écoute les clics sur la carte et met à jour le point central en conséquence.
- Interroge l'API Places:envoie des requêtes à une API backend (
/api/data
) pour récupérer des informations sur les lieux (restaurants, parcs, bars) à proximité de l'emplacement cliqué, à l'aide d'une autorisation Firebase App Check. - Affichage des repères:affiche les données récupérées sur la carte sous forme de repères, avec leur nom et leur icône.
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
- Charge les variables d'environnement (clés API, ID de projet Google) à partir d'un fichier
.env
. - Démarre le serveur,qui écoute les requêtes sur
http://localhost:3000
. - Initialise le SDK Firebase Admin à l'aide des identifiants par défaut de l'application (ADC).
- Reçoit un jeton reCAPTCHA de
script.js
. - Vérifie la validité du jeton reçu.
- Si le jeton est valide, envoie une requête POST à l'API Google Places avec les paramètres de recherche inclus.
- Traite et renvoie la réponse de l'API Places au client.
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. Exécuter l'application
Dans l'environnement de votre choix, exécutez le serveur à partir du terminal, puis accédez à http://localhost:3000.
npm start
Un jeton est créé en tant que variable globale, masqué de la fenêtre du navigateur de l'utilisateur et transmis au serveur pour traitement. Vous trouverez plus d'informations sur le jeton dans les journaux du serveur. | Vous trouverez des informations sur les fonctions du serveur et sa réponse à la requête Nearby Search de l'API Places dans les journaux du serveur. |
Dépannage :
Assurez-vous que l'ID de projet Google est cohérent dans la configuration:
- dans le fichier .env (variable GOOGLE_CLOUD_PROJECT) ;
- Dans la configuration gcloud du terminal:
gcloud config set project your-project-id
- dans la configuration de reCAPTCHA ;
- dans la configuration Firebase ;
Autre
- Créez un jeton de débogage pouvant être utilisé à la place de la clé de site reCAPTCHA dans
script.js
à des fins de test et de dépannage.
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);
}
- Si vous essayez trop d'authentifications infructueuses (par exemple, en utilisant une fausse clé de site reCAPTCHA), vous risquez de déclencher un débit limité temporaire.
FirebaseError: AppCheck: Requests throttled due to 403 error. Attempts allowed again after 01d:00m:00s (appCheck/throttled).
Identifiants ADC
- Vérifiez que vous êtes connecté au bon compte gcloud.
gcloud auth login
- Assurez-vous que les bibliothèques nécessaires sont installées.
npm install @googlemaps/places firebase-admin
- Assurez-vous que la bibliothèque Firebase est chargée dans server.js.
const {GoogleAuth} = require('google-auth-library');
- Développement local: définir l'ADC
gcloud auth application-default login
- Usurpation d'identité: identifiants ADC enregistrés
gcloud auth application-default login --impersonate-service-account your_project@appspot.gserviceaccount.com
- Testez finalement l'ADC localement, en enregistrant le script suivant sous le nom test.js et en l'exécutant dans le terminal:
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. C'est tout, bravo !
Étapes de suivi
Déploiement dans App Engine:
- Préparez votre projet pour le déploiement sur Google App Engine, en apportant les modifications de configuration nécessaires.
- Utilisez l'outil de ligne de commande
gcloud
ou la console App Engine pour déployer votre application.
Améliorez Firebase Authentication:
- Jetons par défaut et jetons personnalisés:implémentez des jetons personnalisés Firebase pour une utilisation plus approfondie des services Firebase.
- Durée de vie du jeton:définissez des durées de vie de jeton appropriées, plus courtes pour les opérations sensibles (jeton Firebase personnalisé jusqu'à une heure), plus longues pour les sessions générales (jeton reCAPTCHA: 30 minutes à 7 heures).
- Explorez les alternatives à reCAPTCHA:vérifiez si DeviceCheck (iOS), SafetyNet (Android) ou App Attest sont adaptés à vos besoins de sécurité.
Intégrez des produits Firebase:
- Realtime Database ou Firestore:si votre application a besoin de synchroniser des données en temps réel ou de fonctionnalités hors connexion, intégrez-la à Realtime Database ou à Firestore.
- Cloud Storage:utilisez Cloud Storage pour stocker et diffuser du contenu généré par les utilisateurs, comme des images ou des vidéos.
- Authentification:utilisez Firebase Authentication pour créer des comptes utilisateur, gérer les sessions de connexion et gérer la réinitialisation des mots de passe.
Développer l'application sur mobile :
- Android et iOS:si vous prévoyez de créer une application mobile, créez des versions pour les plates-formes Android et iOS.
- SDK Firebase:utilisez les SDK Firebase pour Android et iOS pour intégrer facilement les fonctionnalités Firebase à vos applications mobiles.