Mengautentikasi Pengguna di App Engine Menggunakan Firebase


Tutorial ini menunjukkan cara mengambil, memverifikasi, dan menyimpan kredensial pengguna menggunakan Firebase Authentication, lingkungan standar App Engine, dan Datastore.

Dokumen ini akan memandu Anda melalui aplikasi pencatatan sederhana bernama Firestore yang menyimpan catatan pengguna di notebook pribadi mereka. Notebook disimpan per pengguna, dan diidentifikasi oleh ID Firebase Authentication unik setiap pengguna. Aplikasi tersebut memiliki komponen berikut:

  • Frontend mengonfigurasi antarmuka pengguna login dan mengambil ID Firebase Authentication. Frontend ini juga menangani perubahan status autentikasi dan memungkinkan pengguna melihat catatan mereka.

  • FirebaseUI adalah solusi open source drop-in yang menyederhanakan tugas autentikasi dan UI. SDK menangani login pengguna, yang menautkan beberapa penyedia ke satu akun, memulihkan sandi, dan lainnya. Layanan ini menerapkan praktik terbaik autentikasi untuk pengalaman login yang lancar dan aman.

    FirebaseUI
  • Backend memverifikasi status autentikasi pengguna dan menampilkan informasi profil pengguna serta catatan pengguna.

Aplikasi ini menyimpan kredensial pengguna di Datastore dengan menggunakan library klien NDB, tetapi Anda dapat menyimpan kredensial tersebut di database pilihan Anda.

Diagram berikut menunjukkan cara frontend dan backend berkomunikasi satu sama lain dan cara kredensial pengguna berpindah dari Firebase ke database.
Jalur permintaan dengan kredensial pengguna

Firenotes didasarkan pada framework aplikasi web Flask. Aplikasi contoh menggunakan Flask karena kesederhanaan dan kemudahan penggunaannya, tetapi konsep dan teknologi yang dipelajari dapat diterapkan, terlepas dari framework yang Anda gunakan.

Tujuan

Dengan menyelesaikan tutorial ini, Anda akan menyelesaikan hal berikut:

  • Mengonfigurasi antarmuka pengguna Firebase Authentication.
  • Mendapatkan token ID Firebase dan verifikasi menggunakan autentikasi sisi server.
  • Menyimpan kredensial pengguna dan data terkait di Datastore.
  • Membuat kueri database menggunakan library klien NDB.
  • Men-deploy aplikasi ke App Engine.

Biaya

Tutorial ini menggunakan komponen Google Cloud yang dapat ditagih, termasuk:

  • Datastore

Gunakan Kalkulator Harga untuk membuat perkiraan biaya berdasarkan penggunaan yang Anda proyeksikan. Pengguna Google Cloud baru mungkin memenuhi syarat untuk mendapatkan uji coba gratis.

Sebelum memulai

  1. Instal Git, Python 2.7, dan virtualenv. Untuk mengetahui informasi selengkapnya tentang penyiapan lingkungan pengembangan Python, seperti menginstal Python versi terbaru, lihat Menyiapkan Lingkungan Pengembangan Python untuk Google Cloud.
  2. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  3. Di konsol Google Cloud, pada halaman pemilih project, pilih atau buat project Google Cloud.

    Buka pemilih project

  4. Install the Google Cloud CLI.
  5. To initialize the gcloud CLI, run the following command:

    gcloud init
  6. Di konsol Google Cloud, pada halaman pemilih project, pilih atau buat project Google Cloud.

    Buka pemilih project

  7. Install the Google Cloud CLI.
  8. To initialize the gcloud CLI, run the following command:

    gcloud init

Jika Anda telah menginstal dan menginisialisasi SDK ke project yang berbeda, tetapkan project gcloud ke project ID App Engine yang Anda gunakan untuk Firenotes. Baca bagian Mengelola Konfigurasi Google Cloud SDK jika ingin mengetahui perintah khusus untuk mengupdate project dengan alat gcloud.

Membuat clone aplikasi contoh

Untuk mendownload sampel ke komputer lokal:

  1. Buat clone repositori aplikasi contoh ke komputer lokal Anda:

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    Atau, Anda dapat mendownload contoh sebagai file ZIP dan mengekstraknya.

  2. Buka direktori yang berisi kode contoh:

    cd python-docs-samples/appengine/standard/firebase/firenotes
    
    Untuk mengonfigurasi FirebaseUI dan mengaktifkan penyedia identitas:

  3. Tambahkan Firebase ke aplikasi Anda dengan mengikuti langkah-langkah berikut:

    1. Buat project Firebase di Firebase console.
      • Jika Anda tidak memiliki project Firebase yang ada, klik Tambahkan project lalu masukkan nama project Google Cloud yang ada atau nama project baru.
      • Jika Anda sudah memiliki project Firebase yang ingin digunakan, pilih project tersebut dari konsol.
    2. Dari halaman ringkasan project, klik Tambahkan Firebase ke aplikasi web Anda. Jika project Anda sudah memiliki aplikasi, pilih Tambahkan Aplikasi dari halaman ringkasan project.
    3. Gunakan bagian Initialize Firebase dari cuplikan kode yang disesuaikan project Anda untuk mengisi bagian berikut dari file frontend/main.js:

      // Obtain the following from the "Add Firebase to your web app" dialogue
      // Initialize Firebase
      var config = {
        apiKey: "<API_KEY>",
        authDomain: "<PROJECT_ID>.firebaseapp.com",
        databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
        projectId: "<PROJECT_ID>",
        storageBucket: "<BUCKET>.appspot.com",
        messagingSenderId: "<MESSAGING_SENDER_ID>"
      };
  4. Di file frontend/main.js, konfigurasikan widget login FirebaseUI dengan memilih penyedia yang ingin Anda tawarkan kepada pengguna.

    // Firebase log-in widget
    function configureFirebaseLoginWidget() {
      var uiConfig = {
        'signInSuccessUrl': '/',
        'signInOptions': [
          // Leave the lines as is for the providers you want to offer your users.
          firebase.auth.GoogleAuthProvider.PROVIDER_ID,
          firebase.auth.FacebookAuthProvider.PROVIDER_ID,
          firebase.auth.TwitterAuthProvider.PROVIDER_ID,
          firebase.auth.GithubAuthProvider.PROVIDER_ID,
          firebase.auth.EmailAuthProvider.PROVIDER_ID
        ],
        // Terms of service url
        'tosUrl': '<your-tos-url>',
      };
    
      var ui = new firebaseui.auth.AuthUI(firebase.auth());
      ui.start('#firebaseui-auth-container', uiConfig);
    }
  5. Aktifkan penyedia yang telah Anda pilih untuk disimpan di Firebase console dengan mengklik Authentication > Sign-in method. Kemudian, di bagian Sign-in providers, arahkan kursor ke salah satu penyedia, lalu klik ikon pensil.

    Penyedia proses masuk

    1. Klik tombol Aktifkan, dan bagi penyedia identitas pihak ketiga, masukkan ID dan rahasia penyedia dari situs developer penyedia. Dokumentasi Firebase memberikan petunjuk spesifik di bagian "Sebelum memulai" dalam panduan Facebook, Twitter, dan GitHub. Setelah mengaktifkan penyedia, klik Simpan.

      Aktifkan/nonaktifkan tombol

    2. Di Firebase console, pada bagian Authorized Domains, klik Add Domain dan masukkan domain aplikasi Anda di App Engine dalam format berikut:

      [PROJECT_ID].appspot.com
      

      Jangan sertakan http:// sebelum nama domain.

Menginstal dependensi

  1. Buka direktori backend dan selesaikan penyiapan aplikasi:

    cd backend/
    
  2. Instal dependensi ke direktori lib di project Anda:

    pip install -t lib -r requirements.txt
    
  3. Di appengine_config.py, metode vendor.add() mendaftarkan library dalam direktori lib.

Menjalankan aplikasi secara lokal

Untuk menjalankan aplikasi secara lokal, gunakan server pengembangan lokal App Engine:

  1. Tambahkan URL berikut sebagai backendHostURL di main.js:

    http://localhost:8081

  2. Buka direktori utama aplikasi tersebut. Kemudian, mulai server pengembangan:

    dev_appserver.py frontend/app.yaml backend/app.yaml
    
  3. Buka http://localhost:8080/ di browser web.

Mengautentikasi pengguna di server

Setelah menyiapkan project dan melakukan inisialisasi aplikasi untuk pengembangan, Anda dapat mempelajari kode tersebut untuk memahami cara mengambil dan memverifikasi token ID Firebase di server.

Mendapatkan token ID dari Firebase

Langkah pertama dalam autentikasi sisi server adalah mengambil token akses untuk diverifikasi. Permintaan Authentication ditangani dengan pemroses onAuthStateChanged() dari Firebase:

firebase.auth().onAuthStateChanged(function(user) {
  if (user) {
    $('#logged-out').hide();
    var name = user.displayName;

    /* If the provider gives a display name, use the name for the
    personal welcome message. Otherwise, use the user's email. */
    var welcomeName = name ? name : user.email;

    user.getIdToken().then(function(idToken) {
      userIdToken = idToken;

      /* Now that the user is authenicated, fetch the notes. */
      fetchNotes();

      $('#user').text(welcomeName);
      $('#logged-in').show();

    });

  } else {
    $('#logged-in').hide();
    $('#logged-out').show();

  }
});

Ketika pengguna login, metode getToken() Firebase di callback menampilkan token ID Firebase dalam bentuk Token Web JSON (JWT).

Memverifikasi token di server

Setelah pengguna login, layanan frontend akan mengambil catatan apa pun yang ada di notebook pengguna melalui permintaan GET AJAX. Hal ini memerlukan otorisasi untuk mengakses data pengguna, sehingga JWT dikirim di header Authorization permintaan menggunakan skema Bearer:

// Fetch notes from the backend.
function fetchNotes() {
  $.ajax(backendHostUrl + '/notes', {
    /* Set header for the XMLHttpRequest to get data from the web server
    associated with userIdToken */
    headers: {
      'Authorization': 'Bearer ' + userIdToken
    }
  })

Sebelum klien dapat mengakses data server, server Anda harus memverifikasi bahwa token telah ditandatangani Firebase. Anda dapat memverifikasi token ini menggunakan Library Autentikasi Google untuk Python. Gunakan fungsi verify_firebase_token library autentikasi untuk memverifikasi token pemilik dan mengekstrak klaim:

id_token = request.headers["Authorization"].split(" ").pop()
claims = google.oauth2.id_token.verify_firebase_token(
    id_token, HTTP_REQUEST, audience=os.environ.get("GOOGLE_CLOUD_PROJECT")
)
if not claims:
    return "Unauthorized", 401

Setiap penyedia identitas mengirim kumpulan klaim yang berbeda, tetapi masing-masing memiliki setidaknya klaim sub dengan ID pengguna unik dan klaim yang memberikan beberapa informasi profil, seperti name atau email, yang dapat digunakan untuk mempersonalisasi pengalaman pengguna di aplikasi Anda.

Mengelola data pengguna di Datastore

Setelah melakukan autentikasi, Anda harus menyimpan data pengguna agar dapat dipertahankan setelah sesi login berakhir. Bagian berikut menjelaskan cara menyimpan catatan sebagai entity Datastore dan memisahkan entity berdasarkan ID pengguna.

Membuat entity untuk menyimpan data pengguna

Anda dapat membuat entity di Datastore dengan mendeklarasikan class model NDB bersama properti tertentu seperti bilangan bulat atau string. Datastore mengindeks entity berdasarkan jenis; dalam kasus Firenotes, jenis setiap entity adalah Note. Untuk tujuan kueri, setiap Note disimpan dengan nama kunci, yang merupakan ID pengguna yang diperoleh dari klaim sub di bagian sebelumnya.

Kode berikut menunjukkan cara menetapkan properti entity, baik dengan metode konstruktor untuk class model saat entity dibuat maupun melalui penetapan properti individual setelah pembuatan:

data = request.get_json()

# Populates note properties according to the model,
# with the user ID as the key name.
note = Note(parent=ndb.Key(Note, claims["sub"]), message=data["message"])

# Some providers do not provide one of these so either can be used.
note.friendly_id = claims.get("name", claims.get("email", "Unknown"))

Untuk menulis Note yang baru dibuat ke Datastore, panggil metode put() pada objek note.

Mengambil data pengguna

Untuk mengambil data pengguna yang terkait dengan ID pengguna tertentu, gunakan metode query() NDB untuk menelusuri catatan dalam grup entity yang sama di database. Entity dalam grup yang sama, atau jalur ancestor memiliki nama kunci yang sama, yang dalam hal ini adalah ID pengguna.

def query_database(user_id):
    """Fetches all notes associated with user_id.

    Notes are ordered them by date created, with most recent note added
    first.
    """
    ancestor_key = ndb.Key(Note, user_id)
    query = Note.query(ancestor=ancestor_key).order(-Note.created)
    notes = query.fetch()

    note_messages = []

    for note in notes:
        note_messages.append(
            {
                "friendly_id": note.friendly_id,
                "message": note.message,
                "created": note.created,
            }
        )

    return note_messages

Selanjutnya, Anda dapat mengambil data kueri dan menampilkan catatan di klien:

// Fetch notes from the backend.
function fetchNotes() {
  $.ajax(backendHostUrl + '/notes', {
    /* Set header for the XMLHttpRequest to get data from the web server
    associated with userIdToken */
    headers: {
      'Authorization': 'Bearer ' + userIdToken
    }
  }).then(function(data){
    $('#notes-container').empty();
    // Iterate over user data to display user's notes from database.
    data.forEach(function(note){
      $('#notes-container').append($('<p>').text(note.message));
    });
  });
}

Men-deploy aplikasi Anda

Anda telah berhasil mengintegrasikan Firebase Authentication dengan aplikasi App Engine. Untuk melihat aplikasi Anda berjalan di lingkungan produksi yang aktif:

  1. Ubah URL host backend di main.js menjadi https://backend-dot-[PROJECT_ID].appspot.com. Ganti [PROJECT_ID] dengan project ID Anda.
  2. Deploy aplikasi menggunakan antarmuka command line Google Cloud SDK:

    gcloud app deploy backend/index.yaml frontend/app.yaml backend/app.yaml
    
  3. Lihat permohonan secara langsung di https://[PROJECT_ID].appspot.com.

Pembersihan

Agar tidak menimbulkan biaya pada akun Google Cloud Anda untuk resource yang digunakan dalam tutorial ini, hapus project App Engine Anda:

Menghapus project

Cara termudah untuk menghilangkan penagihan adalah dengan menghapus project yang Anda buat untuk tutorial.

Untuk menghapus project:

  1. Di konsol Google Cloud, buka halaman Manage resource.

    Buka Manage resource

  2. Pada daftar project, pilih project yang ingin Anda hapus, lalu klik Delete.
  3. Pada dialog, ketik project ID, lalu klik Shut down untuk menghapus project.

Langkah berikutnya