1. Antes de comenzar
Para garantizar la legitimidad de los usuarios que interactúan con tu aplicación web, implementarás la Verificación de aplicaciones de Firebase y aprovecharás los tokens de JWT de reCAPTCHA para verificar las sesiones de los usuarios. Esta configuración te permitirá controlar de forma segura las solicitudes de la aplicación cliente a la API de Places (nueva).
Qué compilarás.
Para demostrar esto, crearás una app web que muestre un mapa cuando se cargue. También generará un token de reCAPTCHA de forma discreta con el SDK de Firebase. Luego, este token se envía a tu servidor de Node.js, donde Firebase lo valida antes de entregar cualquier solicitud a la API de Places.
Si el token es válido, la Verificación de aplicaciones de Firebase lo almacenará hasta que venza, lo que elimina la necesidad de crear un token nuevo para cada solicitud del cliente. Si el token no es válido, se le pedirá al usuario que vuelva a completar la verificación de reCAPTCHA para obtener un token nuevo.
2. Requisitos previos
Deberás familiarizarte con los siguientes elementos para completar este codelab.
Productos de Google Cloud obligatorios
- Verificación de aplicaciones de Firebase de Google Cloud: Base de datos para la administración de tokens
- Google reCAPTCHA: Creación y verificación de tokens. Es una herramienta que se usa para distinguir a las personas de los bots en los sitios web. Funciona analizando el comportamiento del usuario, los atributos del navegador y la información de la red para generar una puntuación que indique la probabilidad de que el usuario sea un bot. Si la puntuación es lo suficientemente alta, se considera que el usuario es humano y no es necesario realizar ninguna acción adicional. Si la puntuación es baja, es posible que se presente un rompecabezas de CAPTCHA para confirmar la identidad del usuario. Este enfoque es menos intrusivo que los métodos de CAPTCHA tradicionales, lo que hace que la experiencia del usuario sea más fluida.
- (Opcional) Google Cloud App Engine: Entorno de implementación.
Productos obligatorios de Google Maps Platform
En este codelab, usarás los siguientes productos de Google Maps Platform:
- API de Maps JavaScript cargada y mostrada en la app web
- Solicitud de la API de Places (versión nueva) que emite el servidor de backend
Otros requisitos para este codelab
Para completar este codelab, necesitarás las siguientes cuentas, servicios y herramientas:
- Una cuenta de Google Cloud Platform con facturación habilitada
- Una clave de API de Google Maps Platform con la API de Maps JavaScript y Places habilitadas
- Conocimientos básicos de JavaScript, HTML y CSS
- Conocimientos básicos de Node.js
- El editor de texto o IDE que prefieras
3. Prepárate
Configura Google Maps Platform
Si todavía no tienes una cuenta de Google Cloud Platform y un proyecto con la facturación habilitada, consulta la guía Cómo comenzar a utilizar Google Maps Platform para crear una cuenta de facturación y un proyecto.
- En Cloud Console, haz clic en el menú desplegable del proyecto y selecciona el proyecto que deseas usar para este codelab.
- Habilita las API y los SDK de Google Maps Platform necesarios para este codelab en Google Cloud Marketplace. Para hacerlo, sigue los pasos que se indican en este video o esta documentación.
- Genera una clave de API en la página Credenciales de Cloud Console. Puedes seguir los pasos que se indican en este video o esta documentación. Todas las solicitudes a Google Maps Platform requieren una clave de API.
Credencial predeterminada de la aplicación
Usarás el SDK de Firebase Admin para interactuar con tu proyecto de Firebase y realizar solicitudes a la API de Places. Para que funcione, deberás proporcionar credenciales válidas.
Usaremos la autenticación de ADC (credenciales predeterminadas automáticas) para autenticar tu servidor y realizar solicitudes. Como alternativa (no recomendada), puedes crear una cuenta de servicio y almacenar credenciales en tu código.
Definición: Las credenciales predeterminadas de la aplicación (ADC) son un mecanismo que proporciona Google Cloud para autenticar automáticamente tus aplicaciones sin administrar credenciales de forma explícita. Busca credenciales en varias ubicaciones (como variables de entorno, archivos de cuentas de servicio o el servidor de metadatos de Google Cloud) y usa la primera que encuentra.
- En la terminal, usa el siguiente comando que permite que tus aplicaciones accedan de forma segura a los recursos de Google Cloud en nombre del usuario que accedió:
gcloud auth application-default login
- Crearás un archivo .env en la raíz que especifique una variable de proyecto de Google Cloud:
GOOGLE_CLOUD_PROJECT="your-project-id"
Credenciales alternativas (no recomendadas)
Crea una cuenta de servicio
- Pestaña Google Maps Platform > "+Crear credenciales" > Cuenta de servicio
- Agrega el rol de administrador de Firebase AppCheck y, luego, ingresa el nombre de la cuenta de servicio que acabas de escribir, p. ej., firebase-appcheck-codelab@yourproject.iam.gserviceaccount.com
Credenciales
- Haz clic en la cuenta de servicio creada.
- Ve a la pestaña CLAVES para crear una clave > JSON > guarda las credenciales de JSON descargadas. Mueve el archivo xxx.json que se descargó automáticamente a tu carpeta raíz.
- (Próximo capítulo) Asócialo correctamente al archivo server.js de Node.js (firebase-credentials.json).
4. Integración de Firebase App Check
Obtendrás los detalles de configuración de Firebase y las claves secretas de reCAPTCHA.
Los pegarás en la aplicación de demostración y comenzarás el servidor.
Cómo crear una aplicación en Firebase
- Ve al Administrador de proyectos https://console.firebase.google.com (busca los vínculos):
SElecciona el proyecto de Google Cloud que ya se creó (es posible que debas especificar: "Seleccionar el recurso superior").
- Agrega una aplicación desde el menú de la parte superior izquierda (el ícono de ajustes)
Código de inicialización de Firebase
- Guarda el código de inicialización de Firebase para pegarlo en script.js (próximo capítulo) para el cliente
- Registra tu app para permitir que Firebase use tokens de reCAPTCHA v3
https://console.firebase.google.com/u/0/project/YOUR_PROJECT/appcheck/apps
- Elige reCAPTCHA → crea una clave en el sitio web de reCAPTCHA (con los dominios correctos configurados: localhost para el desarrollador de apps).
- Pega el secreto de reCAPTCHA en Firebase AppCheck
- El estado de la app debería cambiar a verde.
5. Aplicación de demostración
- App web cliente: Archivos HTML, JavaScript y CSS
- Servidor: Archivo de Node.js
- Entorno (.env): Claves de API
- Configuración (app.yaml): Es la configuración de implementación de Google App Engine.
Configuración de Node.js:
- Navegar: Abre la terminal y navega al directorio raíz de tu proyecto clonado.
- Instala Node.js (si es necesario): versión 18 o posterior.
node -v # Check installed version
- Inicializa el proyecto: Ejecuta el siguiente comando para inicializar un nuevo proyecto de Node.js y dejar toda la configuración como predeterminada:
npm init
- Instala dependencias: Usa el siguiente comando para instalar las dependencias del proyecto requeridas:
npm install @googlemaps/places firebase-admin express axios dotenv
Configuración: Variables de entorno para el proyecto de Google Cloud
- Creación de archivos de entorno: En el directorio raíz de tu proyecto, crea un archivo llamado
.env
. Este archivo almacenará datos de configuración sensibles y no se debe confirmar en el control de versiones. - Propaga las variables de entorno: Abre el archivo
.env
y agrega las siguientes variables. Para ello, reemplaza los marcadores de posición por los valores reales de tu proyecto de 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. Descripción general del código
index.html
- Carga las bibliotecas de Firebase para crear el token en la app.
<!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
- Obtiene claves de API: Recupera claves de API para Google Maps y la Verificación de aplicaciones de Firebase desde un servidor de backend.
- Inicializa Firebase: Configura Firebase para la autenticación y la seguridad. (configuración de reemplazo → consulta el Capítulo 4).
La duración de validez del token de Verificación de aplicaciones de Firebase, que oscila entre 30 minutos y 7 días, se configura en Firebase console y no se puede alterar si se intenta forzar una actualización del token.
- Activa la Verificación de aplicaciones: Habilita la Verificación de aplicaciones de Firebase para verificar la autenticidad de las solicitudes entrantes.
- Carga la API de Google Maps: Carga de forma dinámica la biblioteca de Google Maps JavaScript para mostrar el mapa.
- Inicializa el mapa: Crea un mapa de Google centrado en una ubicación predeterminada.
- Controla los clics en el mapa: Detecta los clics en el mapa y actualiza el punto central según corresponda.
- Consulta la API de Places: Envía solicitudes a una API de backend (
/api/data
) para recuperar información sobre lugares (restaurantes, parques y bares) cerca de la ubicación en la que se hizo clic, con Firebase App Check para la autorización. - Mostrar marcadores: Traza los datos recuperados en el mapa como marcadores y muestra sus nombres y íconos.
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
- Carga variables de entorno (claves de API, ID de proyecto de Google) desde un archivo
.env
. - Inicia el servidor y escucha las solicitudes en
http://localhost:3000
. - Inicia el SDK de Firebase Admin con las credenciales predeterminadas de la aplicación (ADC).
- Recibe un token de reCAPTCHA de
script.js
. - Verifica la validez del token recibido.
- Si el token es válido, realiza una solicitud POST a la API de Google Places con los parámetros de búsqueda incluidos.
- Procesa y muestra la respuesta de la API de Places al cliente.
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. Ejecuta la aplicación
Desde el entorno que elegiste, ejecuta el servidor desde la terminal y navega a http://localhost:3000.
npm start
Se crea un token como una variable global, se oculta de la ventana del navegador del usuario y se transmite al servidor para su procesamiento. Los detalles del token se pueden encontrar en los registros del servidor. | En los registros del servidor, puedes encontrar detalles sobre las funciones del servidor y su respuesta a la solicitud de Búsqueda Cercana de la API de Places. |
Solución de problemas:
Asegúrate de que el ID del proyecto de Google sea coherente en la configuración:
- en el archivo .env (variable GOOGLE_CLOUD_PROJECT)
- en la configuración de gcloud de la terminal:
gcloud config set project your-project-id
- en la configuración de reCAPTCHA
- en la configuración de Firebase
Otro
- Crea un token de depuración que se pueda usar en lugar de la clave de sitio de reCAPTCHA en
script.js
para realizar pruebas y solucionar problemas.
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 intentas realizar demasiadas autenticaciones fallidas, p. ej., si usas una clave de sitio de reCAPTCHA falsa, es posible que se active una limitación temporal.
FirebaseError: AppCheck: Requests throttled due to 403 error. Attempts allowed again after 01d:00m:00s (appCheck/throttled).
Credenciales de ADC
- Asegúrate de que accediste a la cuenta de gcloud correcta.
gcloud auth login
- Asegúrate de que las bibliotecas necesarias estén instaladas.
npm install @googlemaps/places firebase-admin
- Asegúrate de que se cargue la biblioteca de Firebase en server.js.
const {GoogleAuth} = require('google-auth-library');
- Desarrollo local: Configura ADC
gcloud auth application-default login
- Robo de identidad: Se guardaron las credenciales de ADC
gcloud auth application-default login --impersonate-service-account your_project@appspot.gserviceaccount.com
- Por último, prueba el ADC de forma local, guarda la siguiente secuencia de comandos como test.js y ejecútala en la 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. Eso es todo. ¡Bien hecho!
Pasos de seguimiento
Implementación en App Engine:
- Prepara tu proyecto para la implementación en Google App Engine y realiza los cambios de configuración necesarios.
- Usa la herramienta de línea de comandos de
gcloud
o la consola de App Engine para implementar tu aplicación.
Mejora Firebase Authentication:
- Tokens predeterminados y personalizados: Implementa tokens personalizados de Firebase para usar los servicios de Firebase de forma más profunda.
- Vida útil del token: Establece vidas útiles de token adecuadas, más cortas para operaciones sensibles (token de Firebase personalizado de hasta una hora) y más largas para sesiones generales (token de reCAPTCHA: de 30 min a 7 horas).
- Explora alternativas a reCAPTCHA: Investiga si DeviceCheck (iOS), SafetyNet (Android) o App Attest son adecuados para tus necesidades de seguridad.
Integra productos de Firebase:
- Realtime Database o Firestore: Si tu aplicación necesita sincronización de datos en tiempo real o funciones sin conexión, realiza la integración con Realtime Database o Firestore.
- Cloud Storage: Usa Cloud Storage para almacenar y entregar contenido generado por usuarios, como imágenes o videos.
- Autenticación: Aprovecha Firebase Authentication para crear cuentas de usuario, administrar sesiones de acceso y controlar el restablecimiento de contraseñas.
Expandir a dispositivos móviles:
- Android y iOS: Si planeas tener una app para dispositivos móviles, crea versiones para las plataformas de Android y iOS.
- SDK de Firebase: Usa los SDK de Firebase para Android y iOS para integrar sin problemas las funciones de Firebase en tus aplicaciones para dispositivos móviles.