Iniciar sessão de utilizadores no App Engine

Este tutorial mostra como obter, validar e armazenar credenciais de terceiros através da Identity Platform, do ambiente padrão do App Engine e do Datastore.

Este documento explica passo a passo uma aplicação simples de tomada de notas denominada Firenotes, que armazena as notas dos utilizadores nos respetivos blocos de notas pessoais. Os blocos de notas são armazenados por utilizador e identificados pelo ID exclusivo da Identity Platform de cada utilizador. A aplicação tem os seguintes componentes:

  • O front-end configura a interface do utilizador de início de sessão e obtém o ID da Identity Platform. Também processa alterações ao estado de autenticação e permite que os utilizadores vejam as respetivas notas.

  • A FirebaseUI é uma solução de código aberto e integrada que simplifica a autenticação e as tarefas da IU. O SDK processa o início de sessão do utilizador, a associação de vários fornecedores a uma conta, a recuperação de palavras-passe e muito mais. Implementa práticas recomendadas de autenticação para uma experiência de início de sessão simples e segura.

  • O back-end valida o estado de autenticação do utilizador e devolve as informações do perfil do utilizador, bem como as notas do utilizador.

A aplicação armazena as credenciais do utilizador no Datastore através da biblioteca de cliente NDB, mas pode armazenar as credenciais numa base de dados à sua escolha.

O Firenotes baseia-se na framework de aplicações Web Flask. A app de exemplo usa o Flask devido à sua simplicidade e facilidade de utilização, mas os conceitos e as tecnologias explorados são aplicáveis independentemente da framework que usar.

Objetivos

Ao concluir este tutorial, vai realizar o seguinte:

  • Configure a interface do utilizador com o FirebaseUI para a Identity Platform.
  • Obtenha um token de ID da Identity Platform e valide-o através da autenticação do lado do servidor.
  • Armazenar credenciais de utilizador e dados associados no Datastore.
  • Consultar uma base de dados através da biblioteca cliente NDB.
  • Implemente uma app no App Engine.

Custos

Este tutorial usa componentes faturáveis do Google Cloud, incluindo:

  • Armazenamento de dados
  • Identity Platform

Use a calculadora de preços para gerar uma estimativa de custos com base na sua utilização projetada.

Os novos utilizadores podem ser elegíveis para uma avaliação gratuita. Google Cloud

Antes de começar

  1. Instale o Git, o Python 2.7 e o virtualenv. Para mais informações sobre como configurar o ambiente de desenvolvimento Python, como instalar a versão mais recente do Python, consulte o artigo Configurar um ambiente de desenvolvimento Python para 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. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  4. Install the Google Cloud CLI.

  5. Se estiver a usar um fornecedor de identidade (IdP) externo, tem primeiro de iniciar sessão na CLI gcloud com a sua identidade federada.

  6. Para inicializar a CLI gcloud, execute o seguinte comando:

    gcloud init
  7. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  8. Install the Google Cloud CLI.

  9. Se estiver a usar um fornecedor de identidade (IdP) externo, tem primeiro de iniciar sessão na CLI gcloud com a sua identidade federada.

  10. Para inicializar a CLI gcloud, execute o seguinte comando:

    gcloud init
  11. Se já instalou e inicializou o SDK num projeto diferente, defina o projeto gcloud para o ID do projeto do App Engine que está a usar para o Firenotes. Consulte o artigo Gerir configurações do SDK Google Cloud para ver comandos específicos para atualizar um projeto com a ferramenta gcloud.

    Clonar a app de exemplo

    Para transferir o exemplo para a sua máquina local:

    1. Clone o repositório da aplicação de exemplo para a sua máquina local:

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

      Em alternativa, pode transferir o exemplo como um ficheiro ZIP e extraí-lo.

    2. Navegue para o diretório que contém o exemplo de código:

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

    Adicionar a interface do utilizador

    Para configurar o FirebaseUI para a Identity Platform e ativar os fornecedores de identidade:

    1. Siga estes passos para adicionar o Identity Platform à sua app:

      1. Aceda à Google Cloud consola.
        Aceda à Google Cloud consola
      2. Selecione o Google Cloud projeto que quer usar:
        • Se tiver um projeto existente, selecione-o na lista pendente Selecionar organização na parte superior da página.
        • Se não tiver um Google Cloud projeto existente, crie um novo naGoogle Cloud consola.
      3. Aceda à página Identity Platform Marketplace na Google Cloud consola.
        Aceda à página do Identity Platform Marketplace
      4. Na página do Identity Platform Marketplace, clique em Ativar identidade do cliente.
      5. Aceda à página Utilizadores da identidade do cliente na Google Cloud consola.
        Aceda à página Utilizadores
      6. Na parte superior direita, clique em detalhes da configuração da aplicação.
      7. Copie os detalhes de configuração da aplicação para a sua aplicação 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. Edite o ficheiro backend/app.yaml para adicionar GOOGLE_CLOUD_PROJECT : 'PROJECT_ID' na secção 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. No ficheiro frontend/main.js, configure o widget de início de sessão do FirebaseUI selecionando os fornecedores que quer oferecer aos seus utilizadores.

      // 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. Na Google Cloud consola, ative os fornecedores que escolheu manter:

      1. Aceda à página Fornecedores de identidade do cliente na Google Cloud consola.
        Aceda à página Fornecedores
      2. Clique em Adicionar um fornecedor.
      3. Na lista pendente Selecionar um fornecedor, selecione os fornecedores que quer usar.
      4. Junto a Ativado, clique no botão para ativar o fornecedor.
        • Para fornecedores de identidade de terceiros, introduza o ID e o segredo do fornecedor a partir do site do programador do fornecedor. A documentação do Firebase fornece instruções específicas nas secções "Antes de começar" dos guias do Facebook, Twitter e GitHub.
        • Para integrações SAML e OIDC, consulte a configuração no seu IdP.
    5. Adicione o seu domínio à lista de domínios autorizados na Identity Platform:

      1. Aceda à página Definições de identidade do cliente na Google Cloud consola.
        Aceda à página Definições
      2. Em Domínios autorizados, clique em Adicionar domínio.
      3. Introduza o domínio da sua app no seguinte formato:

        [PROJECT_ID].appspot.com
        

        Não inclua http:// antes do nome de domínio.

    Instalar dependências

    1. Navegue para o diretório backend e conclua a configuração da aplicação:

      cd backend/
      
    2. Instale as dependências num diretório lib no seu projeto:

      pip install -t lib -r requirements.txt
      
    3. Em appengine_config.py, o método vendor.add() regista as bibliotecas no diretório lib.

    Executar a aplicação localmente

    Para executar a aplicação localmente, use o servidor de desenvolvimento local do App Engine:

    1. Adicione o seguinte URL como backendHostURL em main.js:

      http://localhost:8081

    2. Navegue para o diretório raiz da aplicação. Em seguida, inicie o servidor de desenvolvimento:

      dev_appserver.py frontend/app.yaml backend/app.yaml
      
    3. Aceda a http://localhost:8080/ num navegador de Internet.

    Autenticar utilizadores no servidor

    Agora que configurou um projeto e inicializou uma aplicação para desenvolvimento, pode analisar o código para compreender como obter e validar tokens de ID do Identity Platform no servidor.

    Obter um token de ID do Identity Platform

    O primeiro passo na autenticação do lado do servidor é obter um token de acesso para validação. Os pedidos de autenticação são processados com o ouvinte do Identity Platform:onAuthStateChanged()

    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 um utilizador tem sessão iniciada, o método getToken() da Identity Platform no callback devolve um token de ID da Identity Platform sob a forma de um token da Web JSON (JWT).

    Validar tokens no servidor

    Depois de um utilizador iniciar sessão, o serviço de front-end obtém todas as notas existentes no bloco de notas do utilizador através de um pedido AJAX GET. Isto requer autorização para aceder aos dados do utilizador, pelo que o JWT é enviado no cabeçalho Authorization do pedido através do esquema 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
        }
      })

    Antes de o cliente poder aceder aos dados do servidor, o seu servidor tem de verificar se o token está assinado pela Identity Platform. Pode validar este token através da biblioteca de autenticação Google para Python. Use a função verify_firebase_token da biblioteca de autenticação para validar o token de autorização e extrair as reivindicações:

    # 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

    Cada fornecedor de identidade envia um conjunto diferente de reivindicações, mas cada um tem, pelo menos, uma reivindicação sub com um ID do utilizador exclusivo e uma reivindicação que fornece algumas informações do perfil, como name ou email, que pode usar para personalizar a experiência do utilizador na sua app.

    Gerir dados do utilizador no Armazenamento de Dados

    Depois de autenticar um utilizador, tem de armazenar os respetivos dados para que persistam após o fim de uma sessão com sessão iniciada. As secções seguintes explicam como armazenar uma nota como uma entidade do Datastore e segregar entidades por ID do utilizador.

    Criar entidades para armazenar dados do utilizador

    Pode criar uma entidade no Datastore declarando uma classe de modelo NDB com determinadas propriedades, como números inteiros ou strings. O Datastore indexa as entidades por tipo; no caso do Firenotes, o tipo de cada entidade é Note. Para fins de consulta, cada Note é armazenado com um nome da chave, que é o ID do utilizador obtido a partir da reivindicação sub na secção anterior.

    O código seguinte demonstra como definir propriedades de uma entidade, tanto com o método do construtor para a classe do modelo quando a entidade é criada, como através da atribuição de propriedades individuais após a criação:

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

    Para escrever o Note recém-criado no Datastore, chame o método put() no objeto note.

    A obter dados do utilizador

    Para obter dados do utilizador associados a um ID de utilizador específico, use o método NDB query() para pesquisar na base de dados notas no mesmo grupo de entidades. As entidades no mesmo grupo ou no caminho do antepassado partilham um nome de chave comum, que, neste caso, é o ID do utilizador.

    # 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
    
    

    Em seguida, pode obter os dados da consulta e apresentar as notas no cliente:

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

    Implementar a sua app

    Integrou com êxito o Identity Platform com a sua aplicação do App Engine. Para ver a sua aplicação em execução num ambiente de produção publicado:

    1. Altere o URL do anfitrião de back-end em main.js para https://backend-dot-[PROJECT_ID].appspot.com. Substitua [PROJECT_ID] pelo ID do seu projeto.
    2. Implemente a aplicação através da interface de linhas de comando do Google Cloud SDK:

      gcloud app deploy backend/index.yaml frontend/app.yaml backend/app.yaml
      
    3. Veja a aplicação em direto em https://[PROJECT_ID].appspot.com.

    Limpar

    Para evitar incorrer em custos na sua Google Cloud conta pelos recursos usados neste tutorial, elimine o seu projeto do App Engine:

    Eliminar o projeto

    A forma mais fácil de eliminar a faturação é eliminar o projeto que criou para o tutorial.

    Para eliminar o projeto:

    1. In the Google Cloud console, go to the Manage resources page.

      Go to Manage resources

    2. In the project list, select the project that you want to delete, and then click Delete.
    3. In the dialog, type the project ID, and then click Shut down to delete the project.

    O que se segue?