1. Antes de começar
Para garantir a legitimidade dos usuários que interagem com seu aplicativo da Web, implemente o Firebase App Check, usando tokens JWT do reCAPTCHA para verificar as sessões do usuário. Essa configuração vai permitir que você processe com segurança as solicitações do aplicativo cliente para a API Places (nova).
O que você vai criar.
Para demonstrar isso, você vai criar um app da Web que mostra um mapa ao carregar. Ele também vai gerar discretamente um token reCAPTCHA usando o SDK do Firebase. Esse token é enviado para seu servidor Node.js, onde o Firebase o valida antes de atender a qualquer solicitação da API Places.
Se o token for válido, o Firebase App Check vai armazená-lo até que ele expire, eliminando a necessidade de criar um novo token para cada solicitação do cliente. Se o token for inválido, o usuário vai precisar concluir a verificação reCAPTCHA novamente para receber um novo token.
2. Pré-requisitos
Você precisa se familiarizar com os itens abaixo para concluir este codelab.
Produtos obrigatórios do Google Cloud
- Firebase App Check do Google Cloud: banco de dados para gerenciamento de tokens
- Google reCAPTCHA: criação e verificação de tokens. É uma ferramenta usada para distinguir humanos de bots em sites. Ele funciona analisando o comportamento do usuário, os atributos do navegador e as informações de rede para gerar uma pontuação que indica a probabilidade de o usuário ser um bot. Se a pontuação for alta o suficiente, o usuário será considerado humano e nenhuma outra ação será necessária. Se a pontuação for baixa, um quebra-cabeças de CAPTCHA poderá ser apresentado para confirmar a identidade do usuário. Essa abordagem é menos intrusiva do que os métodos tradicionais de CAPTCHA, o que torna a experiência do usuário mais tranquila.
- (Opcional) Google Cloud App Engine: ambiente de implantação.
Produtos obrigatórios da Plataforma Google Maps
Neste codelab, você usará os seguintes produtos da Plataforma Google Maps:
- API Maps JavaScript carregada e exibida no app da Web
- Solicitação da API Places (nova) emitida pelo servidor de back-end
Outros requisitos para este codelab
Para concluir este codelab, você vai precisar das seguintes contas, serviços e ferramentas:
- Uma conta do Google Cloud Platform com o faturamento ativado
- Uma chave de API da Plataforma Google Maps com a API Maps JavaScript e a API Places ativadas
- Conhecimento básico de JavaScript, HTML e CSS
- Conhecimento básico de Node.js
- Um editor de texto ou ambiente de desenvolvimento integrado de sua escolha
3. Começar a configuração
Configurar a Plataforma Google Maps
Caso você ainda não tenha uma conta do Google Cloud Platform e um projeto com faturamento ativado, consulte o guia Primeiros passos com a Plataforma Google Maps para criar uma conta de faturamento e um projeto.
- No Console do Cloud, clique no menu suspenso do projeto e selecione a opção que você quer usar neste codelab.
- Ative as APIs e os SDKs da Plataforma Google Maps necessários para este codelab no Google Cloud Marketplace. Caso precise de ajuda, siga as etapas neste vídeo ou nesta documentação.
- Gere uma chave de API na página Credenciais do Console do Cloud. Você pode seguir as etapas neste vídeo ou nesta documentação. Todas as solicitações à Plataforma Google Maps exigem uma chave de API.
Application Default Credentials
Você vai usar o SDK Admin do Firebase para interagir com seu projeto do Firebase e fazer solicitações à API Places. Para que ele funcione, é necessário fornecer credenciais válidas.
Usaremos a autenticação ADC (credenciais padrão automáticas) para autenticar seu servidor e fazer solicitações. Como alternativa (não recomendada), você pode criar uma conta de serviço e armazenar credenciais no código.
Definição: o Application Default Credentials (ADC) é um mecanismo fornecido pelo Google Cloud para autenticar automaticamente seus aplicativos sem gerenciar credenciais explicitamente. Ele procura credenciais em vários locais (como variáveis de ambiente, arquivos de conta de serviço ou servidor de metadados do Google Cloud) e usa a primeira que encontrar.
- No Terminal, use o comando abaixo, que permite que seus aplicativos acessem com segurança os recursos do Google Cloud em nome do usuário conectado:
gcloud auth application-default login
- Você vai criar um arquivo .env na raiz que especifica uma variável do projeto do Google Cloud:
GOOGLE_CLOUD_PROJECT="your-project-id"
Credenciais alternativas (não recomendadas)
Criar uma conta de serviço
- Guia "Plataforma Google Maps" > "+Criar credenciais" > Conta de serviço
- Adicione a função de administrador do Firebase AppCheck e insira o nome da conta de serviço que você acabou de digitar, por exemplo: firebase-appcheck-codelab@yourproject.iam.gserviceaccount.com
Credenciais
- Clique na conta de serviço criada.
- Na guia "KEYS", selecione "Create a Key" > "JSON" > salve as credenciais JSON salvas. Mova o arquivo xxx.json que foi salvo automaticamente para a pasta raiz
- (Próxima aula) Nomeie corretamente no arquivo nodejs server.js (firebase-credentials.json).
4. Integração do Firebase App Check
Você vai receber detalhes de configuração do Firebase e chaves secretas do reCAPTCHA.
Você vai colá-los no aplicativo de demonstração e iniciar o servidor.
Criar um aplicativo no Firebase
- Acesse o administrador do projeto https://console.firebase.google.com (encontre links):
Selecione o projeto do Google Cloud que já foi criado. Talvez seja necessário especificar: "Selecionar o recurso pai".
- Adicione um aplicativo no menu superior esquerdo (engrenagem)
Código de inicialização do Firebase
- Salvar o código de inicialização do Firebase para colar no script.js (próximo capítulo) no lado do cliente
- Registrar seu app para permitir que o Firebase use tokens do reCAPTCHA v3
https://console.firebase.google.com/u/0/project/YOUR_PROJECT/appcheck/apps
- Escolha reCAPTCHA → crie uma chave no site reCAPTCHA (com os domínios certos configurados: localhost para desenvolvimento de apps)
- Cole o segredo do reCAPTCHA no Firebase App Check
- O status do app vai ficar verde.
5. Aplicativo de demonstração
- App da Web do cliente:arquivos HTML, JavaScript e CSS
- Servidor:arquivo Node.js
- Ambiente (.env): chaves de API
- Configuração (app.yaml): configurações de implantação do Google App Engine
Configuração do Node.js:
- Navegar: abra o terminal e navegue até o diretório raiz do projeto clonado.
- Instale o Node.js (se necessário): versão 18 ou mais recente.
node -v # Check installed version
- Inicializar projeto:execute o comando a seguir para inicializar um novo projeto Node.js, deixando todas as configurações como padrão:
npm init
- Instalar dependências:use o comando abaixo para instalar as dependências do projeto necessárias:
npm install @googlemaps/places firebase-admin express axios dotenv
Configuração: variáveis de ambiente para o projeto do Google Cloud
- Criação de arquivos de ambiente:no diretório raiz do projeto, crie um arquivo chamado
.env
. Esse arquivo vai armazenar dados de configuração sensíveis e não deve ser confirmado no controle de versão. - Preencher variáveis de ambiente:abra o arquivo
.env
e adicione as seguintes variáveis, substituindo os marcadores de posição pelos valores reais do seu projeto do 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. Visão geral do código
index.html
- Carrega as bibliotecas do Firebase para criar o token no 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
- Busca chaves de API:recupera chaves de API do Google Maps e do Firebase App Check em um servidor de back-end.
- Inicializa o Firebase:configura o Firebase para autenticação e segurança. (Substitua a configuração → consulte o Capítulo 4).
A duração de validade do token do Firebase App Check, que varia de 30 minutos a 7 dias, é configurada no Console do Firebase e não pode ser alterada ao forçar a atualização do token.
- Ativa o App Check:permite que o Firebase App Check verifique a autenticidade das solicitações recebidas.
- Carrega a API Google Maps:carrega dinamicamente a biblioteca JavaScript do Google Maps para mostrar o mapa.
- Inicializa o mapa:cria um mapa do Google centralizado em um local padrão.
- Processa cliques no mapa:detecta cliques no mapa e atualiza o ponto central de acordo.
- Consultas da API Places:envia solicitações para uma API de back-end (
/api/data
) para buscar informações sobre lugares (restaurantes, parques, bares) perto do local clicado, usando o Firebase App Check para autorização. - Mostrar marcadores:representa os dados buscados no mapa como marcadores, mostrando os nomes e ícones deles.
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
- Carrega variáveis de ambiente (chaves de API, ID do projeto do Google) de um arquivo
.env
. - Inicia o servidor,detectando solicitações em
http://localhost:3000
. - Inicializa o SDK Admin do Firebase usando as credenciais padrão do aplicativo (ADC).
- Recebe um token reCAPTCHA de
script.js
. - Verifica a validade do token recebido.
- Se o token for válido, faça uma solicitação POST para a API Google Places com os parâmetros de pesquisa incluídos.
- Processa e retorna a resposta da API Places para o 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. Execute o aplicativo
No ambiente escolhido, execute o servidor no terminal e acesse http://localhost:3000.
npm start
Um token é criado como uma variável global, oculta da janela do navegador do usuário e transmitida ao servidor para processamento. Os detalhes do token podem ser encontrados nos registros do servidor. | Os detalhes sobre as funções do servidor e a resposta à solicitação de pesquisa por proximidade da API Places estão nos registros do servidor. |
Solução de problemas:
Verifique se o ID do projeto do Google está consistente na configuração:
- no arquivo .env (variável GOOGLE_CLOUD_PROJECT)
- na configuração da gcloud do terminal:
gcloud config set project your-project-id
- na configuração do reCAPTCHA
- na configuração do Firebase
Outro
- Crie um token de depuração que possa ser usado no lugar da chave do site reCAPTCHA em
script.js
para fins de teste e solução de 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);
}
- Tentar muitas autenticações sem sucesso, por exemplo, usar uma chave de site falsa do reCAPTCHA, pode acionar um limite temporário.
FirebaseError: AppCheck: Requests throttled due to 403 error. Attempts allowed again after 01d:00m:00s (appCheck/throttled).
Credenciais do ADC
- Verifique se você está na conta do gcloud certa
gcloud auth login
- Verifique se as bibliotecas necessárias estão instaladas
npm install @googlemaps/places firebase-admin
- Verifique se a biblioteca do Firebase foi carregada em server.js.
const {GoogleAuth} = require('google-auth-library');
- Desenvolvimento local: definir o ADC
gcloud auth application-default login
- Impersonate: as credenciais do ADC foram salvas
gcloud auth application-default login --impersonate-service-account your_project@appspot.gserviceaccount.com
- Por fim, teste o ADC localmente, salvando o script a seguir como "test.js" e executando no 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. Pronto, parabéns!
Etapas de acompanhamento
Implantação no App Engine:
- Prepare seu projeto para implantação no Google App Engine, fazendo as mudanças de configuração necessárias.
- Use a ferramenta de linha de comando
gcloud
ou o console do App Engine para implantar seu aplicativo.
Melhorar a autenticação do Firebase:
- Tokens padrão x tokens personalizados:implemente tokens personalizados do Firebase para usar melhor os serviços do Firebase.
- Tempo de vida do token:defina o tempo de vida adequado para operações sensíveis (token personalizado do Firebase até uma hora) e sessões gerais (token do reCAPTCHA: 30 minutos a 7 horas).
- Conheça as alternativas ao reCAPTCHA:verifique se o DeviceCheck (iOS), o SafetyNet (Android) ou o App Attest são adequados para suas necessidades de segurança.
Integrar produtos do Firebase:
- Realtime Database ou Firestore:se o app precisar de sincronização de dados em tempo real ou recursos off-line, faça a integração com o Realtime Database ou o Firestore.
- Cloud Storage:use o Cloud Storage para armazenar e exibir conteúdo gerado pelo usuário, como imagens ou vídeos.
- Autenticação:use o Firebase Authentication para criar contas de usuário, gerenciar sessões de login e processar redefinições de senha.
Expandir para dispositivos móveis:
- Android e iOS:se você planeja ter um app para dispositivos móveis, crie versões para as plataformas Android e iOS.
- SDKs do Firebase:use os SDKs do Firebase para Android e iOS para integrar perfeitamente os recursos do Firebase aos seus apps para dispositivos móveis.