Authentifier des utilisateurs sur App Engine à l'aide de Firebase


Ce tutoriel explique comment récupérer, valider et stocker les identifiants utilisateur avec Firebase Authentication, l'environnement standard App Engine et Datastore.

Ce document décrit une application de prise de notes simple appelée "Firenotes", qui stocke les notes des utilisateurs dans leurs notebooks personnels. Les notebooks sont stockés par utilisateur et identifiés par l'identifiant Firebase Authentication unique de chaque utilisateur. L'application est constituée des composants suivants :

  • L'interface configure l'UI de connexion et récupère l'identifiant Firebase Authentication. Elle gère également les changements d'état d'authentification et permet aux utilisateurs de voir leurs notes.

  • FirebaseUI est une solution Open Source prête à l'emploi qui simplifie les tâches d'authentification et d'UI. Le SDK gère la connexion des utilisateurs, associe plusieurs fournisseurs à un compte, récupère les mots de passe, et bien plus encore. Cette solution met en œuvre les bonnes pratiques d'authentification pour assurer une expérience de connexion fluide et sécurisée.

    FirebaseUI
  • Le serveur vérifie l'état d'authentification de l'utilisateur et renvoie les informations du profil utilisateur, ainsi que les notes de l'utilisateur.

L'application stocke les identifiants utilisateur dans Datastore à l'aide de la bibliothèque cliente NDB, mais vous pouvez stocker les identifiants dans la base de données de votre choix.

Le schéma suivant représente les communications entre l'interface et le backend, ainsi que le transfert des identifiants utilisateur depuis Firebase vers la base de données.
Cheminement d'une requête avec identifiants utilisateur

L'application Firenotes est basée sur le framework d'application Web Flask. L'exemple d'application utilise Flask en raison de sa simplicité et de sa facilité d'utilisation, mais les concepts et les technologies explorés sont applicables quel que soit le framework que vous utilisez.

Objectifs

Dans ce tutoriel, vous réaliserez les tâches suivantes :

  • Configurer l'interface utilisateur de Firebase Authentication
  • Obtenir un jeton d'identification Firebase et le valider à l'aide de l'authentification côté serveur
  • Stocker les identifiants utilisateur et les données associées dans Datastore
  • Interroger une base de données à l'aide de la bibliothèque cliente NDB.
  • Déployer une application sur App Engine.

Coûts

Ce tutoriel utilise des composants facturables de Google Cloud, dont :

  • Datastore

Obtenez une estimation des coûts en fonction de votre utilisation prévue à l'aide du simulateur de coût. Les nouveaux utilisateurs de Google Cloud peuvent bénéficier d'un essai gratuit.

Avant de commencer

  1. Installez Git, Python 2.7 et virtualenv. Pour en savoir plus sur la configuration de votre environnement de développement Python, par exemple sur l'installation de la dernière version de Python, consultez la page Configurer un environnement de développement Python pour Google Cloud.
  2. Connectez-vous à votre compte Google Cloud. Si vous débutez sur Google Cloud, créez un compte pour évaluer les performances de nos produits en conditions réelles. Les nouveaux clients bénéficient également de 300 $ de crédits gratuits pour exécuter, tester et déployer des charges de travail.
  3. Dans Google Cloud Console, sur la page de sélection du projet, sélectionnez ou créez un projet Google Cloud.

    Accéder au sélecteur de projet

  4. Installez Google Cloud CLI.
  5. Pour initialiser gcloudCLI, exécutez la commande suivante :

    gcloud init
  6. Dans Google Cloud Console, sur la page de sélection du projet, sélectionnez ou créez un projet Google Cloud.

    Accéder au sélecteur de projet

  7. Installez Google Cloud CLI.
  8. Pour initialiser gcloudCLI, exécutez la commande suivante :

    gcloud init

Si vous avez déjà installé et initialisé le SDK dans un autre projet, définissez le projet gcloud sur l'ID de projet App Engine que vous utilisez pour Firenotes. Consultez la page Gérer les configurations du SDK Google Cloud pour voir des commandes spécifiques permettant de mettre à jour un projet avec l'outil gcloud.

Cloner l'exemple d'application

Pour télécharger l'exemple sur l'ordinateur local :

  1. Clonez le dépôt de l'exemple d'application sur votre ordinateur local :

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

    Vous pouvez également télécharger l'exemple en tant que fichier zip et l'extraire.

  2. Accédez au répertoire qui contient l'exemple de code :

    cd python-docs-samples/appengine/standard/firebase/firenotes
    
    Pour configurer FirebaseUI et activer les fournisseurs d'identité :

  3. Ajoutez Firebase à votre application en procédant comme suit :

    1. Créez un projet Firebase dans la console Firebase.
      • Si vous ne disposez d'aucun projet Firebase, cliquez sur Ajouter un projet et entrez un nom de projet Google Cloud existant ou un nouveau nom de projet.
      • Si vous souhaitez utiliser un projet Firebase existant, sélectionnez-le dans la console.
    2. Dans la page de présentation du projet, cliquez sur Ajouter Firebase à votre application Web. Si votre projet contient déjà une application, sélectionnez Ajouter une application dans la page de présentation du projet.
    3. Utilisez la section Initialize Firebase de l'extrait de code personnalisé de votre projet pour remplir la section suivante du fichier 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. Dans le fichier frontend/main.js, configurez le widget de connexion FirebaseUI en sélectionnant les fournisseurs que vous souhaitez proposer à vos utilisateurs.

    // 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. Activez les fournisseurs que vous souhaitez conserver dans la console Firebase en cliquant sur Authentication > Sign-in method (Authentification > Mode de connexion). Ensuite, sous Sign-in providers (Fournisseurs de connexion), placez le curseur sur un fournisseur, puis cliquez sur l'icône en forme de crayon.

    Fournisseurs de connexion

    1. Activez ou désactivez le bouton Enable (Activer). Saisissez l'ID du fournisseur d'identité tiers et son code secret à partir de son site pour les développeurs. Les documents Firebase donnent à ce sujet des instructions spécifiques dans les sections "Avant de commencer" des guides Facebook, Twitter et GitHub. Après avoir activé un fournisseur, cliquez sur Save (Enregistrer).

      Bouton d'activation/de désactivation

    2. Dans la console Firebase, sous Domaines autorisés, cliquez sur Ajouter un domaine et saisissez le domaine de votre application sur App Engine au format suivant :

      [PROJECT_ID].appspot.com
      

      Veillez à ne pas insérer http:// avant le nom de domaine.

Installer les dépendances

  1. Accédez au répertoire backend et terminez la configuration de l'application comme suit :

    cd backend/
    
  2. Installez les dépendances dans un répertoire lib de votre projet :

    pip install -t lib -r requirements.txt
    
  3. Dans appengine_config.py, la méthode vendor.add() enregistre les bibliothèques dans le répertoire lib.

Exécuter votre application en local

Pour exécuter l'application en local, utilisez le serveur de développement local App Engine :

  1. Ajoutez l'URL suivante en tant que backendHostURL dans main.js :

    http://localhost:8081

  2. Accédez au répertoire racine de l'application. Ensuite, démarrez le serveur de développement :

    dev_appserver.py frontend/app.yaml backend/app.yaml
    
  3. Visitez http://localhost:8080/ dans un navigateur Web.

Authentifier des utilisateurs auprès du serveur

Maintenant que vous avez configuré un projet et initialisé une application pour le développement, vous pouvez parcourir le code pour comprendre comment récupérer et vérifier les jetons d'identification Firebase sur le serveur.

Obtenir un jeton d'identification de Firebase

La première étape de l'authentification côté serveur consiste à récupérer un jeton d'accès à valider. Les requêtes d'authentification sont traitées avec l'écouteur onAuthStateChanged() de 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();

  }
});

Lorsqu'un utilisateur est connecté, la méthode Firebase getToken() dans le rappel renvoie un jeton d'ID Firebase sous la forme d'un jeton Web JSON (JWT, JSON Web Token).

Valider des jetons sur le serveur

Une fois qu'un utilisateur s'est connecté, le service d'interface récupère toutes les notes existantes dans le notebook de l'utilisateur via une requête AJAX GET. Cela nécessite une autorisation pour accéder aux données de l'utilisateur. Le jeton JWT est donc envoyé dans l'en-tête Authorization de la requête avec le schéma 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
    }
  })

Le serveur doit vérifier que le jeton est signé par Firebase avant que le client ne puisse accéder aux données du serveur. Vous pouvez valider ce jeton à l'aide de la bibliothèque d'authentification Google pour Python. Utilisez la fonction verify_firebase_token de la bibliothèque d'authentification pour valider le jeton de support et extraire les revendications :

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

Chaque fournisseur d'identité envoie un ensemble différent de revendications. Chaque ensemble comporte au moins une revendication sub avec un ID utilisateur unique et une revendication fournissant certaines informations de profil, telles que name ou email, dont vous pouvez vous servir pour personnaliser l'expérience utilisateur sur votre application.

Gérer les données utilisateur dans Datastore

Après avoir authentifié un utilisateur, vous devez stocker ses données pour qu'elles puissent persister une fois la session ouverte terminée. Les sections suivantes expliquent comment stocker une note en tant qu'entité Datastore et séparer les entités par ID utilisateur.

Créer des entités pour stocker des données utilisateur

Vous pouvez créer une entité dans Datastore en déclarant une classe de modèle NDB avec certaines propriétés, telles que des entiers ou des chaînes. Datastore indexe les entités par genre. Dans le cas de l'application Firenotes, le genre de chaque entité est Note. À des fins d'interrogation, chaque Note est stockée avec un nom de clé qui correspond à l'ID utilisateur obtenu à partir de la revendication sub de la section précédente.

Le code suivant montre comment définir les propriétés d'une entité, lors de la création de l'entité avec la méthode constructeur de la classe de modèle et après sa création via l'affectation de propriétés individuelles :

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

Pour écrire la nouvelle Note dans Datastore, appelez la méthode put() sur l'objet note.

Récupérer des données utilisateur

Pour récupérer les données utilisateur associées à un ID utilisateur particulier, appelez la méthode query() de la bibliothèque NDB pour rechercher dans la base de données des notes appartenant au même groupe d'entités. Les entités du même groupe, ou ayant le même chemin d'ancêtre, partagent un nom de clé commun, qui correspond dans ce cas à l'ID utilisateur.

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

Vous pouvez ensuite récupérer les données de requête et afficher les notes dans le client :

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

Déployer l'application

Vous avez intégré avec succès Firebase Authentication à votre application App Engine. Pour voir votre application s'exécuter dans un environnement de production actif, procédez comme suit :

  1. Remplacez l'URL de l'hôte de backend dans le fichier main.js par https://backend-dot-[PROJECT_ID].appspot.com. Remplacez [PROJECT_ID] par l'ID du projet.
  2. Déployez l'application à l'aide de l'interface de ligne de commande du SDK Google Cloud :

    gcloud app deploy backend/index.yaml frontend/app.yaml backend/app.yaml
    
  3. Consultez l'application en ligne à l'adresse https://[PROJECT_ID].appspot.com.

Nettoyer

Pour éviter que les ressources utilisées dans ce tutoriel soient facturées sur votre compte Google Cloud, supprimez votre projet App Engine comme suit :

Supprimer le projet

Le moyen le plus simple d'empêcher la facturation est de supprimer le projet que vous avez créé pour ce tutoriel.

Pour supprimer le projet :

  1. Dans la console Google Cloud, accédez à la page Gérer les ressources.

    Accéder à la page Gérer les ressources

  2. Dans la liste des projets, sélectionnez le projet que vous souhaitez supprimer, puis cliquez sur Supprimer.
  3. Dans la boîte de dialogue, saisissez l'ID du projet, puis cliquez sur Arrêter pour supprimer le projet.

Étapes suivantes

  • Découvrez des architectures de référence, des schémas et des bonnes pratiques concernant Google Cloud. Consultez notre Centre d'architecture cloud.