1. Trước khi bắt đầu
Để đảm bảo tính hợp pháp của người dùng tương tác với ứng dụng web, bạn sẽ triển khai tính năng Kiểm tra ứng dụng Firebase, tận dụng mã thông báo JWT reCAPTCHA để xác minh phiên người dùng. Chế độ thiết lập này sẽ cho phép bạn xử lý các yêu cầu từ ứng dụng khách đến Places API (mới) một cách an toàn.

Sản phẩm bạn sẽ tạo ra.
Để minh hoạ điều này, bạn sẽ tạo một ứng dụng web hiển thị bản đồ khi tải. Thao tác này cũng sẽ tạo một mã thông báo reCAPTCHA một cách kín đáo bằng Firebase SDK. Sau đó, mã thông báo này sẽ được gửi đến máy chủ Node.js của bạn, nơi Firebase xác thực mã thông báo này trước khi thực hiện bất kỳ yêu cầu nào đối với Places API.
Nếu mã thông báo hợp lệ, Kiểm tra ứng dụng Firebase sẽ lưu trữ mã thông báo đó cho đến khi hết hạn, giúp bạn không cần tạo mã thông báo mới cho mỗi yêu cầu của máy khách. Nếu mã thông báo không hợp lệ, người dùng sẽ được nhắc hoàn tất lại quy trình xác minh bằng reCAPTCHA để nhận mã thông báo mới.
2. Điều kiện tiên quyết
Bạn cần làm quen với những mục dưới đây để hoàn tất Lớp học lập trình này. 
Các sản phẩm bắt buộc của Google Cloud
- Google Cloud Kiểm tra ứng dụng Firebase: cơ sở dữ liệu để quản lý mã thông báo
- Google reCAPTCHA: tạo và xác minh mã thông báo. Đây là một công cụ dùng để phân biệt người dùng thực với bot trên các trang web. Cơ chế này hoạt động bằng cách phân tích hành vi người dùng, thuộc tính trình duyệt và thông tin mạng để tạo ra một điểm số cho biết khả năng người dùng là một bot. Nếu điểm số đủ cao, người dùng được coi là người dùng thực và không cần thực hiện thêm hành động nào. Nếu điểm số thấp, một câu đố CAPTCHA có thể xuất hiện để xác nhận danh tính của người dùng. Phương pháp này ít xâm nhập hơn so với các phương pháp CAPTCHA truyền thống, giúp trải nghiệm người dùng mượt mà hơn.
- (Không bắt buộc) Google Cloud App Engine: môi trường triển khai.
Các sản phẩm bắt buộc của Google Maps Platform
Trong Lớp học lập trình này, bạn sẽ sử dụng các sản phẩm sau đây của Google Maps Platform:
- API JavaScript của Maps được tải và hiển thị trong ứng dụng web
- Yêu cầu Places API (Mới) do máy chủ phụ trợ phát hành
Các yêu cầu khác đối với Lớp học lập trình này
Để hoàn thành Lớp học lập trình này, bạn cần có các tài khoản, dịch vụ và công cụ sau:
- Tài khoản Google Cloud Platform có bật tính năng thanh toán
- Khoá API Google Maps Platform có API JavaScript của Maps và Places được bật
- Kiến thức cơ bản về JavaScript, HTML và CSS
- Kiến thức cơ bản về Node.js
- Trình chỉnh sửa văn bản hoặc IDE mà bạn chọn
3. Bắt đầu thiết lập
Thiết lập Google Maps Platform
Nếu bạn chưa có tài khoản Google Cloud Platform và dự án có bật tính năng thanh toán, vui lòng xem hướng dẫn Bắt đầu sử dụng Google Maps Platform để tạo tài khoản thanh toán và dự án.
- Trong Cloud Console, hãy nhấp vào trình đơn thả xuống dự án rồi chọn dự án mà bạn muốn sử dụng cho lớp học lập trình này.

- Bật các API và SDK của Google Maps Platform cần thiết cho lớp học lập trình này trong Google Cloud Marketplace. Để thực hiện việc này, hãy làm theo các bước trong video này hoặc tài liệu này.
- Tạo khoá API trên trang Thông tin xác thực của Cloud Console. Bạn có thể làm theo các bước trong video này hoặc tài liệu này. Tất cả yêu cầu đối với Google Maps Platform đều cần có khoá API.
Thông tin xác thực mặc định của ứng dụng
Bạn sẽ sử dụng SDK của Firebase dành cho quản trị viên để tương tác với dự án Firebase cũng như đưa ra các yêu cầu cho Places API và cần cung cấp thông tin đăng nhập hợp lệ để SDK này hoạt động.
Chúng tôi sẽ sử dụng Xác thực ADC (Thông tin xác thực mặc định tự động) để xác thực máy chủ của bạn nhằm đưa ra các yêu cầu. Ngoài ra (không nên dùng), bạn có thể tạo một tài khoản dịch vụ và lưu trữ thông tin đăng nhập trong mã của mình.
Định nghĩa: Thông tin xác thực mặc định của ứng dụng (ADC) là một cơ chế mà Google Cloud cung cấp để tự động xác thực các ứng dụng của bạn mà không cần quản lý thông tin xác thực một cách rõ ràng. Nó tìm kiếm thông tin đăng nhập ở nhiều vị trí (chẳng hạn như biến môi trường, tệp tài khoản dịch vụ hoặc máy chủ siêu dữ liệu Google Cloud) và sử dụng thông tin đăng nhập đầu tiên mà nó tìm thấy.
- Trong Terminal, hãy sử dụng lệnh bên dưới để cho phép các ứng dụng của bạn truy cập an toàn vào các tài nguyên của Google Cloud thay cho người dùng hiện đang đăng nhập:
gcloud auth application-default login
- Bạn sẽ tạo một tệp .env ở thư mục gốc để chỉ định một biến Dự án trên Google Cloud:
GOOGLE_CLOUD_PROJECT="your-project-id"
Thông tin đăng nhập thay thế (Không nên dùng)
Tạo một tài khoản dịch vụ
- Thẻ Google Maps Platform > "+Tạo thông tin đăng nhập" > Tài khoản dịch vụ
- Thêm vai trò Quản trị viên AppCheck của Firebase, sau đó nhập tên tài khoản dịch vụ mà bạn vừa nhập, tức là: firebase-appcheck-codelab@yourproject.iam.gserviceaccount.com
Thông tin xác thực
- Nhấp vào tài khoản dịch vụ đã tạo
- Chuyển đến thẻ KEYS (Khoá) để tạo khoá > JSON > lưu thông tin đăng nhập JSON đã tải xuống. Di chuyển tệp xxx.json được tải xuống tự động vào thư mục gốc
- (Chương tiếp theo) Đặt tên chính xác cho tệp nodejs server.js (firebase-credentials.json)
4. Tích hợp Firebase AppCheck
Bạn sẽ nhận được thông tin chi tiết về cấu hình Firebase và khoá bí mật reCAPTCHA.
Bạn sẽ dán các khoá này vào ứng dụng minh hoạ và khởi động máy chủ.
Tạo một ứng dụng trong Firebase
- Chuyển đến trang Quản trị dự án https://console.firebase.google.com (tìm các đường liên kết):
CHỌN dự án trên Google Cloud đã được tạo (bạn có thể phải chỉ định: "Chọn tài nguyên mẹ")"

- Thêm một ứng dụng từ Trình đơn (biểu tượng bánh răng) ở trên cùng bên trái

Mã khởi chạy Firebase
- Lưu mã khởi chạy Firebase để dán vào script.js (chương tiếp theo) cho phía máy khách

- Đăng ký ứng dụng của bạn để cho phép Firebase sử dụng mã thông báo reCAPTCHA v3
https://console.firebase.google.com/u/0/project/YOUR_PROJECT/appcheck/apps

- Chọn reCAPTCHA → tạo khoá trong trang web reCAPTCHA (đã định cấu hình đúng miền: localhost cho quá trình phát triển ứng dụng)

- Dán khoá bí mật reCAPTCHA vào Firebase AppCheck

- Trạng thái ứng dụng sẽ chuyển sang màu xanh lục

5. Ứng dụng minh hoạ
- Ứng dụng web phía máy khách: Tệp HTML, JavaScript, CSS
- Máy chủ: Tệp Node.js
- Môi trường (.env): Khoá API
- Cấu hình (app.yaml): Chế độ cài đặt triển khai Google App Engine
Thiết lập Node.js:
- Điều hướng: Mở Terminal rồi chuyển đến thư mục gốc của dự án bạn đã sao chép.
- Cài đặt Node.js (nếu cần): phiên bản 18 trở lên.
node -v # Check installed version
- Khởi chạy dự án: Chạy lệnh sau để khởi chạy một dự án Node.js mới, giữ nguyên tất cả các chế độ cài đặt mặc định:
npm init
- Cài đặt các phần phụ thuộc: Sử dụng lệnh sau để cài đặt các phần phụ thuộc bắt buộc của dự án:
npm install @googlemaps/places firebase-admin express axios dotenv
Cấu hình: Biến môi trường cho dự án trên Google Cloud
- Tạo tệp môi trường: Trong thư mục gốc của dự án, hãy tạo một tệp có tên là
.env. Tệp này sẽ lưu trữ dữ liệu cấu hình nhạy cảm và không được xác nhận (commit) vào hệ thống quản lý phiên bản. - Điền các biến môi trường: Mở tệp
.envrồi thêm các biến sau, thay thế phần giữ chỗ bằng các giá trị thực trong Dự án Google Cloud của bạn:
# 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. Tổng quan về mã
index.html
- Tải các thư viện firebase để tạo mã thông báo trong ứng dụng
<!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
- Tìm nạp khoá API: Truy xuất khoá API cho Google Maps và Kiểm tra ứng dụng Firebase từ một máy chủ phụ trợ.
- Khởi động Firebase: Thiết lập Firebase để xác thực và bảo mật. (Thay thế cấu hình → xem Chương 4).
Thời hạn hiệu lực của mã thông báo Kiểm tra ứng dụng Firebase (từ 30 phút đến 7 ngày) được định cấu hình trong bảng điều khiển của Firebase và không thể thay đổi bằng cách cố gắng buộc làm mới mã thông báo.
- Kích hoạt Kiểm tra ứng dụng: Bật Kiểm tra ứng dụng Firebase để xác minh tính xác thực của các yêu cầu đến.
- Tải API Google Maps: Tải thư viện JavaScript của Google Maps một cách linh động để hiển thị bản đồ.
- Khởi động Maps: Tạo một Google Maps có vị trí mặc định ở trung tâm.
- Xử lý lượt nhấp vào bản đồ: Lắng nghe các lượt nhấp trên bản đồ và cập nhật điểm giữa cho phù hợp.
- Truy vấn Places API: Gửi yêu cầu đến một API phụ trợ (
/api/data) để tìm nạp thông tin về các địa điểm (nhà hàng, công viên, quán bar) gần vị trí được nhấp, sử dụng Kiểm tra ứng dụng Firebase để uỷ quyền. - Hiển thị điểm đánh dấu: Vẽ dữ liệu đã tìm nạp trên bản đồ dưới dạng điểm đánh dấu, cho biết tên và biểu tượng của các điểm đánh dấu đó.
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
- Tải các biến môi trường (khoá API, mã dự án Google) từ tệp
.env. - Khởi động máy chủ, lắng nghe các yêu cầu trên
http://localhost:3000. - Khởi chạy SDK của Firebase dành cho quản trị viên bằng Thông tin xác thực mặc định của ứng dụng (ADC).
- Nhận mã thông báo reCAPTCHA từ
script.js. - Xác minh tính hợp lệ của mã thông báo nhận được.
- Nếu mã thông báo hợp lệ, sẽ gửi yêu cầu POST đến Google Places API kèm theo các thông số tìm kiếm.
- Xử lý và trả về phản hồi từ Places API cho ứng dụng khách.
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. Chạy ứng dụng
Trong môi trường bạn chọn, hãy chạy máy chủ từ thiết bị đầu cuối và chuyển đến http://localhost:3000
npm start
Mã thông báo được tạo dưới dạng một biến toàn cục, ẩn khỏi cửa sổ trình duyệt của người dùng và được truyền đến máy chủ để xử lý. Bạn có thể xem thông tin chi tiết về mã thông báo trong nhật ký máy chủ. | Bạn có thể xem thông tin chi tiết về các hàm và phản hồi của máy chủ đối với yêu cầu Nearby Search của Places API trong nhật ký máy chủ. |
Khắc phục sự cố:
Đảm bảo mã dự án trên Google nhất quán trong quá trình thiết lập:
- trong tệp .env (biến GOOGLE_CLOUD_PROJECT)
- trong cấu hình gcloud của thiết bị đầu cuối:
gcloud config set project your-project-id
- trong chế độ thiết lập reCAPTCHA

- trong quá trình thiết lập Firebase

Khác
- Tạo mã thông báo gỡ lỗi có thể dùng thay cho khoá trang web reCAPTCHA trong
script.jscho mục đích kiểm thử và khắc phục sự cố.

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);
}
- Việc thử xác thực quá nhiều lần không thành công (ví dụ: sử dụng khoá trang reCAPTCHA giả) có thể kích hoạt tính năng điều tiết tạm thời.
FirebaseError: AppCheck: Requests throttled due to 403 error. Attempts allowed again after 01d:00m:00s (appCheck/throttled).
Thông tin đăng nhập ADC
- Đảm bảo bạn đang sử dụng đúng tài khoản gcloud
gcloud auth login
- Đảm bảo bạn đã cài đặt các thư viện cần thiết
npm install @googlemaps/places firebase-admin
- Đảm bảo rằng thư viện Firebase được tải trong server.js
const {GoogleAuth} = require('google-auth-library');
- Phát triển cục bộ: thiết lập ADC
gcloud auth application-default login
- Giả mạo: Đã lưu thông tin đăng nhập ADC
gcloud auth application-default login --impersonate-service-account your_project@appspot.gserviceaccount.com
- Cuối cùng, hãy kiểm thử ADC cục bộ, lưu tập lệnh sau dưới dạng test.js và chạy trong 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. Vậy là xong, bạn đã làm rất tốt!
Các bước tiếp theo
Triển khai cho App Engine:
- Chuẩn bị dự án của bạn để triển khai cho Google App Engine, thực hiện mọi thay đổi cần thiết về cấu hình.
- Sử dụng công cụ dòng lệnh
gcloudhoặc bảng điều khiển App Engine để triển khai ứng dụng.
Nâng cao tính năng Xác thực Firebase:
- Mã thông báo mặc định so với mã thông báo tuỳ chỉnh: Triển khai mã thông báo tuỳ chỉnh của Firebase để sử dụng các dịch vụ của Firebase một cách chuyên sâu hơn.
- Thời gian tồn tại của mã thông báo: Đặt thời gian tồn tại thích hợp cho mã thông báo, ngắn hơn đối với các hoạt động nhạy cảm (mã thông báo Firebase tuỳ chỉnh tối đa một giờ), dài hơn đối với các phiên chung (mã thông báo reCAPTCHA: từ 30 phút đến 7 giờ).
- Khám phá các lựa chọn thay thế cho reCAPTCHA: Tìm hiểu xem DeviceCheck (iOS), SafetyNet (Android) hoặc App Attest có phù hợp với nhu cầu bảo mật của bạn hay không.
Tích hợp các sản phẩm của Firebase:
- Cơ sở dữ liệu theo thời gian thực hoặc Firestore: Nếu ứng dụng của bạn cần đồng bộ hoá dữ liệu theo thời gian thực hoặc có các chức năng khi không có mạng, hãy tích hợp với Cơ sở dữ liệu theo thời gian thực hoặc Firestore.
- Cloud Storage: Sử dụng Cloud Storage để lưu trữ và phân phát nội dung do người dùng tạo, chẳng hạn như hình ảnh hoặc video.
- Xác thực: Tận dụng Xác thực Firebase để tạo tài khoản người dùng, quản lý phiên đăng nhập và xử lý việc đặt lại mật khẩu.
Mở rộng sang thiết bị di động:
- Android và iOS: Nếu bạn dự định có một ứng dụng di động, hãy tạo các phiên bản cho cả nền tảng Android và iOS.
- SDK Firebase: Sử dụng SDK Firebase cho Android và iOS để tích hợp liền mạch các tính năng của Firebase vào ứng dụng di động.

