Accesso degli utenti su App Engine

Questo tutorial mostra come recuperare, verificare e archiviare le credenziali di terze parti utilizzando Identity Platform, l'ambiente standard di App Engine e Datastore.

Questo documento ti guida attraverso una semplice applicazione per prendere appunti chiamata Firenotes, che memorizza le note degli utenti nei loro quaderni personali. Notebooks vengono memorizzati per utente e identificati dall'ID Identity Platform univoco di ciascun utente. L'applicazione è costituita dai seguenti componenti:

  • Il frontend configura l'interfaccia utente di accesso e recupera l'ID Identity Platform. Gestisce anche le modifiche dello stato di autenticazione e consente agli utenti di visualizzare le proprie note.

  • FirebaseUI è una soluzione open source integrata che semplifica l'autenticazione e le attività dell'interfaccia utente. L'SDK gestisce l'accesso degli utenti, il collegamento di più provider a un account, il recupero delle password e altro ancora. Implementa le best practice di autenticazione per un'esperienza di accesso fluida e sicura.

  • Il backend verifica lo stato di autenticazione dell'utente e restituisce le informazioni del profilo utente, nonché le note dell'utente.

L'applicazione archivia le credenziali utente in Datastore utilizzando la libreria client NDB, ma puoi archiviarle in un database a tua scelta.

Firenotes si basa sul framework per applicazioni web Flask. L'app di esempio utilizza Flask per la sua semplicità e facilità d'uso, ma i concetti e le tecnologie esplorati sono applicabili indipendentemente dal framework utilizzato.

Clonazione dell'app di esempio

Per scaricare il campione sulla tua macchina locale:

  1. Clona il repository dell'applicazione di esempio sulla tua macchina locale:

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

    In alternativa, puoi scaricare il campione come file ZIP ed estrarlo.

  2. Vai alla directory che contiene il codice campione:

    cd python-docs-samples/appengine/standard/firebase/firenotes
    

Aggiunta dell'interfaccia utente

Per configurare FirebaseUI per Identity Platform e attivare i provider di identità:

  1. Aggiungi Identity Platform alla tua app seguendo questi passaggi:

    1. Vai alla consoleGoogle Cloud .
      Vai alla console Google Cloud
    2. Seleziona il progetto Google Cloud che vuoi utilizzare:
      • Se hai un progetto esistente, selezionalo nell'elenco a discesa Seleziona organizzazione nella parte superiore della pagina.
      • Se non hai un progetto Google Cloud esistente, creane uno nuovo nella consoleGoogle Cloud .
    3. Vai alla pagina Identity Platform Marketplace nella console Google Cloud .
      Vai alla pagina Identity Platform Marketplace
    4. Nella pagina Identity Platform Marketplace, fai clic su Abilita identità cliente.
    5. Vai alla pagina Utenti di Customer Identity nella console Google Cloud .
      Vai alla pagina Utenti
    6. In alto a destra, fai clic sui dettagli di configurazione dell'applicazione.
    7. Copia i dettagli di configurazione dell'applicazione nella tua applicazione web.

      // This code is for illustration purposes only.
      
      // 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>"
      };
  2. Modifica il file backend/app.yaml per aggiungere GOOGLE_CLOUD_PROJECT : 'PROJECT_ID' nella sezione env_variables:

    # Copyright 2021 Google LLC
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #      http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    # This code is designed for Python 2.7 and
    # the App Engine first-generation Runtime which has reached End of Support.
    
    runtime: python27
    api_version: 1
    threadsafe: true
    service: backend
    
    handlers:
    - url: /.*
      script: main.app
    
    env_variables:
      GAE_USE_SOCKETS_HTTPLIB : 'true'
    
  3. Nel file frontend/main.js, configura il widget di accesso FirebaseUI selezionando i provider che vuoi offrire ai tuoi utenti.

    // This code is for illustration purposes only.
    
    // 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);
    }
  4. Nella console Google Cloud , attiva i fornitori che hai scelto di mantenere:

    1. Vai alla pagina Provider di Customer Identity nella console Google Cloud .
      Vai alla pagina Provider
    2. Fai clic su Aggiungi un provider.
    3. Nell'elenco a discesa Seleziona un fornitore, seleziona i fornitori che vuoi utilizzare.
    4. Accanto ad Attivato, fai clic sul pulsante per attivare il provider.
      • Per i provider di identità di terze parti, inserisci l'ID e il secret del provider dal sito per sviluppatori del provider. La documentazione di Firebase fornisce istruzioni specifiche nelle sezioni "Prima di iniziare" delle guide di Facebook, Twitter e GitHub.
      • Per le integrazioni SAML e OIDC, consulta la configurazione del tuo IdP.
  5. Aggiungi il tuo dominio all'elenco dei domini autorizzati in Identity Platform:

    1. Vai alla pagina Impostazioni di Customer Identity nella console Google Cloud .
      Vai alla pagina Impostazioni
    2. In Domini autorizzati, fai clic su Aggiungi dominio.
    3. Inserisci il dominio della tua app nel seguente formato:

      [PROJECT_ID].appspot.com
      

      Non includere http:// prima del nome di dominio.

Installazione delle dipendenze

  1. Vai alla directory backend e completa la configurazione dell'applicazione:

    cd backend/
    
  2. Installa le dipendenze in una directory lib nel tuo progetto:

    pip install -t lib -r requirements.txt
    
  3. In appengine_config.py, il metodo vendor.add() registra le librerie nella directory lib.

Esecuzione dell'applicazione in locale

Per eseguire l'applicazione localmente, utilizza il server di sviluppo locale di App Engine:

  1. Aggiungi il seguente URL come backendHostURL in main.js:

    http://localhost:8081

  2. Vai alla directory principale dell'applicazione. Quindi, avvia il server di sviluppo:

    dev_appserver.py frontend/app.yaml backend/app.yaml
    
  3. Visita la pagina http://localhost:8080/ in un browser web.

Autenticazione degli utenti sul server

Ora che hai configurato un progetto e inizializzato un'applicazione per lo sviluppo, puoi esaminare il codice per capire come recuperare e verificare i token ID di Identity Platform sul server.

Ottenere un token ID da Identity Platform

Il primo passaggio dell'autenticazione lato server consiste nel recuperare un token di accesso da verificare. Le richieste di autenticazione vengono gestite con il listener onAuthStateChanged() di Identity Platform:

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();

  }
});

Quando un utente ha eseguito l'accesso, il metodo getToken() di Identity Platform nel callback restituisce un token ID di Identity Platform sotto forma di token web JSON (JWT).

Verifica dei token sul server

Dopo l'accesso di un utente, il servizio frontend recupera le note esistenti nel blocco note dell'utente tramite una richiesta AJAX GET. Ciò richiede l'autorizzazione per accedere ai dati dell'utente, pertanto il JWT viene inviato nell'intestazione Authorization della richiesta utilizzando lo schema 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
    }
  })

Prima che il client possa accedere ai dati del server, il server deve verificare che il token sia firmato da Identity Platform. Puoi verificare questo token utilizzando la libreria di autenticazione Google per Python. Utilizza la funzione verify_firebase_token della libreria di autenticazione per verificare il token di autenticazione e estrarre le rivendicazioni:

# This code is for illustration purposes only.

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

Ogni IdP invia un insieme diverso di attestazioni, ma ognuno ha almeno un'attestazione sub con un ID utente univoco e un'attestazione che fornisce alcune informazioni del profilo, come name o email, che puoi utilizzare per personalizzare l'esperienza utente nella tua app.

Gestione dei dati utente in Datastore

Dopo aver autenticato un utente, devi archiviare i suoi dati in modo che vengano conservati dopo la fine di una sessione con accesso. Le sezioni seguenti spiegano come archiviare una nota come entità Datastore e separare le entità per ID utente.

Creazione di entità per archiviare i dati utente

Puoi creare un'entità in Datastore dichiarando una classe modello NDB con determinate proprietà come numeri interi o stringhe. Datastore indicizza le entità per tipo; nel caso di Firenotes, il tipo di ogni entità è Note. Ai fini delle query, ogni Note viene memorizzato con un nome chiave, ovvero l'ID utente ottenuto dalla rivendicazione sub nella sezione precedente.

Il seguente codice mostra come impostare le proprietà di un'entità, sia con il metodo costruttore per la classe del modello quando l'entità viene creata sia tramite l'assegnazione di singole proprietà dopo la creazione:

# This code is for illustration purposes only.

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"))

Per scrivere il Note appena creato in Datastore, chiama il metodo put() sull'oggetto note.

Recupero dei dati utente

Per recuperare i dati utente associati a un determinato ID utente, utilizza il metodo NDB query() per cercare nel database le note nello stesso gruppo di entità. Le entità nello stesso gruppo o percorso degli antenati condividono un nome di chiave comune, che in questo caso è l'ID utente.

# This code is for illustration purposes only.

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

Puoi quindi recuperare i dati della query e visualizzare le note nel client:

// This code is for illustration purposes only.

// 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));
    });
  });
}

Deployment dell'app

Hai integrato correttamente Identity Platform con la tua applicazione App Engine. Per visualizzare l'applicazione in esecuzione in un ambiente di produzione live:

  1. Modifica l'URL host di backend in main.js in https://backend-dot-[PROJECT_ID].appspot.com. Sostituisci [PROJECT_ID] con il tuo ID progetto.
  2. Esegui il deployment dell'applicazione utilizzando l'interfaccia a riga di comando di Google Cloud SDK:

    gcloud app deploy backend/index.yaml frontend/app.yaml backend/app.yaml
    
  3. Visualizza l'applicazione live all'indirizzo https://[PROJECT_ID].appspot.com.