Firebase AppCheck ve reCAPTCHA ile Places API isteklerini doğrulama

1. Başlamadan önce

Web uygulamanızla etkileşime geçen kullanıcıların meşruluğunu sağlamak için kullanıcı oturumlarını doğrulamak amacıyla reCAPTCHA JWT jetonlarından yararlanarak Firebase Uygulama Kontrolü'nü uygularsınız. Bu kurulum, istemci uygulamasından Places API (Yeni)'ye gelen istekleri güvenli bir şekilde işlemenize olanak tanır.

b40cfddb731786fa.png

Canlı Bağlantı

Oluşturacağınız uygulama.

Bunu göstermek için yüklendiğinde bir harita gösteren bir web uygulaması oluşturacaksınız. Ayrıca Firebase SDK'sını kullanarak gizli bir reCAPTCHA jetonu oluşturur. Bu jeton daha sonra Node.js sunucunuza gönderilir. Firebase, Places API'ye gönderilen istekleri yerine getirmeden önce bu jetonu doğrular.

Jeton geçerliyse Firebase Uygulama Kontrolü, süresi dolana kadar jetonu depolar. Böylece her istemci isteği için yeni bir jeton oluşturma ihtiyacı ortadan kalkar. Jeton geçersizse kullanıcıdan yeni bir jeton almak için reCAPTCHA doğrulamasını tekrar tamamlaması istenir.

2. Ön koşullar

Bu Codelab'i tamamlamak için aşağıdaki öğelerle ilgili bilgi sahibi olmanız gerekir. daea823b6bc38b67.png

Zorunlu Google Cloud Ürünleri

  • Google Cloud Firebase Uygulama Kontrolü: jeton yönetimi için veritabanı
  • Google reCAPTCHA: jeton oluşturma ve doğrulama. Web sitelerinde gerçek kişileri botlardan ayırt etmek için kullanılan bir araçtır. Bu model, kullanıcı davranışını, tarayıcı özelliklerini ve ağ bilgilerini analiz ederek kullanıcının bot olma olasılığını gösteren bir puan oluşturur. Puan yeterince yüksekse kullanıcı gerçek kişi olarak kabul edilir ve başka işlem yapılması gerekmez. Puan düşükse kullanıcının kimliğini doğrulamak için bir CAPTCHA bulmacası gösterilebilir. Bu yaklaşım, geleneksel CAPTCHA yöntemlerine kıyasla daha az müdahalecidir ve kullanıcı deneyimini daha sorunsuz hale getirir.
  • (İsteğe bağlı) Google Cloud App Engine: dağıtım ortamı.

Zorunlu Google Haritalar Platformu Ürünleri

Bu Codelab'de aşağıdaki Google Haritalar Platformu ürünlerini kullanacaksınız:

Bu Codelab için diğer koşullar

Bu Codelab'i tamamlamak için aşağıdaki hesaplara, hizmetlere ve araçlara ihtiyacınız vardır:

  • Faturalandırmanın etkin olduğu bir Google Cloud Platform hesabı
  • Maps JavaScript API ve Yerler'in etkin olduğu bir Google Haritalar Platformu API anahtarı
  • JavaScript, HTML ve CSS hakkında temel düzeyde bilgi
  • Node.js hakkında temel bilgi
  • Tercih ettiğiniz bir metin düzenleyici veya IDE

3. Hazırlanın

Google Haritalar Platformu'nu ayarlama

Google Cloud Platform hesabınız ve faturalandırmanın etkin olduğu bir projeniz yoksa faturalandırma hesabı ve proje oluşturmak için lütfen Google Haritalar Platformu'nu kullanmaya başlama kılavuzunu inceleyin.

  1. Cloud Console'da proje açılır menüsünü tıklayın ve bu kod laboratuvarını kullanmak istediğiniz projeyi seçin.

e7ffad81d93745cd.png

  1. Google Cloud Marketplace'te bu kod laboratuvarının çalışması için gereken Google Haritalar Platformu API'lerini ve SDK'larını etkinleştirin. Bunun için bu videodaki veya bu dokümanlardaki adımları uygulayın.
  2. Cloud Console'un Kimlik Bilgileri sayfasında bir API anahtarı oluşturun. Bu videodaki veya bu dokümanlardaki adımları uygulayabilirsiniz. Google Haritalar Platformu'na yapılan tüm istekler için API anahtarı gerekir.

Uygulama Varsayılan Kimlik Bilgileri

Firebase Admin SDK'sını kullanarak Firebase projenizle etkileşim kurabilir ve Places API'ye istek gönderebilirsiniz. Bu SDK'nın çalışması için geçerli kimlik bilgileri sağlamanız gerekir.

İstek göndermek için sunucunuzun kimliğini doğrulamak üzere ADC Kimlik Doğrulaması'nı (Otomatik Varsayılan Kimlik Bilgileri) kullanırız. Alternatif olarak (önerilmez) bir hizmet hesabı oluşturabilir ve kimlik bilgilerini kodunuzda saklayabilirsiniz.

Tanım: Uygulama Varsayılan Kimlik Bilgileri (ADC), kimlik bilgilerini açıkça yönetmeden uygulamalarınızın kimliğini otomatik olarak doğrulamak için Google Cloud tarafından sağlanan bir mekanizmadır. Çeşitli konumlarda (ör. ortam değişkenleri, hizmet hesabı dosyaları veya Google Cloud meta veri sunucusu) kimlik bilgilerini arar ve bulduğu ilk kimlik bilgisini kullanır.

  • Terminalinizde, uygulamalarınızın şu anda oturum açmış kullanıcı adına Google Cloud kaynaklarına güvenli bir şekilde erişmesine olanak tanıyan aşağıdaki komutu kullanın:
gcloud auth application-default login
  • Kök dizinde, bir Google Cloud projesi değişkenini belirten bir .env dosyası oluşturursunuz:
GOOGLE_CLOUD_PROJECT="your-project-id"

Hizmet hesabı oluşturma

Kimlik bilgileri

  • Oluşturulan hizmet hesabını tıklayın.
  • ANAHTARLAR sekmesine gidip Anahtar Oluştur > JSON > indirilen JSON kimlik bilgilerini kaydedin. Otomatik olarak indirilen xxx.json dosyasını kök klasörünüze taşıyın.
  • (Sonraki Bölüm) nodejs dosyasında server.js (firebase-credentials.json) dosyasını doğru şekilde adlandırın

4. Firebase AppCheck Entegrasyonu

Firebase yapılandırma ayrıntılarını ve reCAPTCHA gizli anahtarlarını alırsınız.

Bunları demo uygulamasına yapıştırıp sunucuyu başlatırsınız.

Firebase'de uygulama oluşturma

Daha önce oluşturulmuş Google Cloud projesini SEÇİN ("Üst kaynağı seçme"yi belirtmeniz gerekebilir)"

a6d171c6d7e98087.png a16010ba102cc90b.png

  • Sol üstteki menüden (çark) uygulama ekleyin.

18e5a7993ad9ea53.png 4632158304652118.png

Firebase başlatma kodu

  • İstemci tarafı için script.js'ye (sonraki bölüm) yapıştırmak üzere Firebase başlatma kodunu kaydedin.

f10dcf6f5027e9f0.png

  • Firebase'in reCAPTCHA s3 jetonlarını kullanmasına izin vermek için uygulamanızı kaydetme

https://console.firebase.google.com/u/0/project/YOUR_PROJECT/appcheck/apps

da7efe203ce4142c.png

  • reCAPTCHA'yı seçin → reCAPTCHA web sitesinde bir anahtar oluşturun (doğru alanlar yapılandırılmış olarak: uygulama geliştirici için localhost)

b47eab131617467.png e6bddef9d5cf5460.png

  • reCAPTCHA Gizli Anahtarı'nı Firebase AppCheck'e yapıştırma

a63bbd533a1b5437.png

  • Uygulama durumu yeşile dönecektir.

4f7962b527b78ee5.png

5. Demo uygulaması

  • İstemci web uygulaması: HTML, JavaScript, CSS dosyaları
  • Sunucu: Node.js dosyası
  • Ortam (.env): API anahtarları
  • Yapılandırma (app.yaml): Google App Engine dağıtım ayarları

Node.js Kurulumu:

  • Gezinme: Terminalinizi açıp klonlanan projenizin kök dizinine gidin.
  • Node.js'yi yükleyin (gerekirse): 18 veya sonraki bir sürüm.
node -v  # Check installed version
  • Projeyi başlatma: Tüm ayarları varsayılan olarak bırakarak yeni bir Node.js projesini başlatmak için aşağıdaki komutu çalıştırın:
npm init 
  • Bağımlılık yükleme: Gerekli proje bağımlılıklarını yüklemek için aşağıdaki komutu kullanın:
npm install @googlemaps/places firebase-admin express axios dotenv

Yapılandırma: Google Cloud Projesi için Ortam Değişkenleri

  • Ortam dosyası oluşturma: Projenizin kök dizininde .env adlı bir dosya oluşturun. Bu dosya hassas yapılandırma verilerini depolar ve sürüm kontrolüne eklenmemelidir.
  • Ortam değişkenlerini doldurun: .env dosyasını açın ve yer tutucuları Google Cloud projenizdeki gerçek değerlerle değiştirerek aşağıdaki değişkenleri ekleyin:
# 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. Koda genel bakış

index.html

  • Uygulamada jeton oluşturmak için Firebase kitaplıklarını yükler
<!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

  • API anahtarlarını getirir: Google Haritalar ve Firebase Uygulama Kontrolü API anahtarlarını arka uç sunucudan alır.
  • Firebase'i başlatır: Firebase'i kimlik doğrulama ve güvenlik için ayarlar. (Yapılandırmayı değiştirin → 4. Bölüm'e bakın).

30 dakika ile 7 gün arasında değişen Firebase Uygulama Kontrolü jetonunun geçerlilik süresi Firebase konsolunda yapılandırılır ve jeton yenilemeyi zorlamaya çalışarak değiştirilemez.

  • Uygulama Kontrolü'nü etkinleştirir: Gelen isteklerin gerçekliğini doğrulamak için Firebase Uygulama Kontrolü'nü etkinleştirir.
  • Google Haritalar API'yi yükler: Haritayı görüntülemek için Google Haritalar JavaScript kitaplığını dinamik olarak yükler.
  • Haritayı başlatır: Varsayılan bir konumun ortasına yerleştirilmiş bir Google Haritası oluşturur.
  • Harita tıklamalarını işler: Haritada tıklamaları dinler ve merkez noktasını buna göre günceller.
  • Places API'yi sorgulayarak: Yetkilendirme için Firebase App Check'i kullanarak tıklanan konumun yakınındaki yerler (restoranlar, parklar, barlar) hakkında bilgi almak üzere arka uç API'ye (/api/data) istek gönderir.
  • İşaretçileri Gösterir: Getirilen verileri haritada işaretçiler olarak gösterir. İşaretçilerin adlarını ve simgelerini de gösterir.
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

  • .env dosyasından ortam değişkenlerini (API anahtarları, Google proje kimliği) yükler.
  • http://localhost:3000 adresinde istekleri dinleyerek sunucuyu başlatır.
  • Uygulama Varsayılan Kimlik Bilgileri'ni (ADC) kullanarak Firebase Admin SDK'sını başlatır.
  • script.js'den reCAPTCHA jetonu alır.
  • Alınan jetonun geçerliliğini doğrular.
  • Jeton geçerliyse arama parametrelerini ekleyerek Google Places API'ye bir POST isteği gönderir.
  • Places API'den istemciye yanıtı işler ve döndürür.
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. Uygulamayı çalıştırın

Seçtiğiniz ortamda, terminalden sunucuyu çalıştırın ve http://localhost:3000 adresine gidin.

npm start 

Genel bir değişken olarak oluşturulan jeton, kullanıcının tarayıcı penceresinden gizlenir ve işleme alınması için sunucuya iletilir. Jetonun ayrıntılarını sunucu günlüklerinde bulabilirsiniz.

Sunucunun işlevleri ve Places API Yakında Arama isteğine verdiği yanıt hakkında ayrıntılı bilgi sunucu günlüklerinde bulunabilir.

Sorun giderme:

Google proje kimliğinin kurulumda tutarlı olduğundan emin olun:

  • .env dosyasında (GOOGLE_CLOUD_PROJECT değişkeni)
  • terminal gcloud yapılandırmasında:
gcloud config set project your-project-id
  • reCaptcha kurulumunda

e6bddef9d5cf5460.png

  • Firebase kurulumunda

7e17bfbcb8007763.png

Diğer

  • Test ve sorun giderme amacıyla script.js içinde reCAPTCHA site anahtarı yerine kullanılabilecek bir hata ayıklama jetonu oluşturun.

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);
}
  • Çok fazla başarısız kimlik doğrulama denemesi (ör. yanlış recaptcha site anahtarı kullanmak) geçici olarak akış kısıtlamasına neden olabilir.
FirebaseError: AppCheck: Requests throttled due to 403 error. Attempts allowed again after 01d:00m:00s (appCheck/throttled).

ADC Kimlik Bilgileri

  • Doğru gcloud hesabında olduğunuzdan emin olun
gcloud auth login 
  • Gerekli kitaplıkların yüklü olduğundan emin olun
npm install @googlemaps/places firebase-admin
  • server.js dosyasında Firebase kitaplığının yüklendiğinden emin olun.
const {GoogleAuth} = require('google-auth-library');
gcloud auth application-default login
  • Kimliğe bürünme: ADC kimlik bilgileri kaydedildi
gcloud auth application-default login --impersonate-service-account your_project@appspot.gserviceaccount.com
  • Son olarak ADC'yi yerel olarak test etmek için aşağıdaki komut dosyasını test.js olarak kaydedip Terminal'de çalıştırın: 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. Hepsi bu kadar. Tebrikler.

Sonraki adımlar

App Engine'e dağıtım:

  • Gerekli yapılandırma değişikliklerini yaparak projenizi Google App Engine'a dağıtıma hazırlayın.
  • Uygulamanızı dağıtmak için gcloud komut satırı aracını veya App Engine konsolunu kullanın.

Firebase Authentication'i geliştirme:

  • Varsayılan ve özel jetonlar: Firebase hizmetlerini daha ayrıntılı şekilde kullanmak için Firebase özel jetonlarını uygulayın.
  • Jeton geçerlilik süresi: Hassas işlemler için daha kısa (özel Firebase jetonu bir saate kadar), genel oturumlar için daha uzun (reCAPTCHA jetonu: 30 dakika ila 7 saat) uygun jeton geçerlilik sürelerini ayarlayın.
  • ReCAPTCHA'ya Alternatifleri Keşfedin: DeviceCheck (iOS), SafetyNet (Android) veya App Attest'in güvenlik ihtiyaçlarınıza uygun olup olmadığını araştırın.

Firebase ürünlerini entegre etme:

  • Realtime Database veya Firestore: Uygulamanızın gerçek zamanlı veri senkronizasyonu veya çevrimdışı özellikler kullanması gerekiyorsa Realtime Database veya Firestore ile entegrasyon yapın.
  • Cloud Storage: Kullanıcı tarafından oluşturulan resim veya video gibi içerikleri depolamak ve sunmak için Cloud Storage'ı kullanın.
  • Kimlik doğrulama: Kullanıcı hesapları oluşturmak, giriş oturumlarını yönetmek ve şifre sıfırlama işlemlerini gerçekleştirmek için Firebase Authentication'ten yararlanın.

Mobil cihazlara yönelik olarak genişletin:

  • Android ve iOS: Mobil uygulama oluşturmayı planlıyorsanız hem Android hem de iOS platformları için sürümler oluşturun.
  • Firebase SDK'ları: Firebase özelliklerini mobil uygulamalarınıza sorunsuz bir şekilde entegre etmek için Android ve iOS için Firebase SDK'larını kullanın.