Como fazer login de usuários no App Engine

Neste tutorial, mostramos como recuperar, verificar e armazenar credenciais de terceiros usando o Identity Platform, o ambiente padrão do App Engine e o Datastore.

Neste documento, você conhecerá um aplicativo simples de anotações, o Firenotes, que armazena as anotações dos usuários nos respectivos notebooks. Os notebooks são armazenados por usuário e identificados pelo ID exclusivo do Identity Platform de cada usuário. O aplicativo tem os seguintes componentes:

  • O front-end configura a interface do usuário de login e recupera o ID do Identity Platform. Ele também processa alterações no estado de autenticação e permite que os usuários vejam as próprias anotações.

  • A FirebaseUI é uma solução de código aberto que simplifica as tarefas de autenticação e IU. O SDK lida com o login do usuário, vinculando vários provedores a uma conta, recuperando senhas e muito mais. Ele implementa as práticas recomendadas de autenticação para fornecer uma experiência de login tranquila e segura.

  • O back-end verifica o estado de autenticação do usuário e retorna as informações de perfil dele, bem como as respectivas anotações.

O aplicativo armazena credenciais de usuário no Datastore usando a biblioteca de cliente NDB, mas é possível armazenar as credenciais em um banco de dados de sua escolha.

O Firenotes é baseado no framework de aplicativos da Web Flask. No aplicativo de exemplo, optamos pela Flask devido à simplicidade e à facilidade de uso, mas as tecnologias e os conceitos explorados são aplicáveis independentemente da biblioteca usada.

Objetivos

Após completar este tutorial, você saberá como:

  • configurar a interface do usuário com a FirebaseUI para o Identity Platform;
  • conseguir um token de ID do Identity Platform e fazer a verificação dele usando a autenticação do servidor;
  • armazenar credenciais de usuário e dados associados no Datastore;
  • consultar um banco de dados usando a biblioteca de cliente NDB;
  • implantar um aplicativo no App Engine.

Custos

Neste tutorial, há componentes faturáveis do Google Cloud, entre eles:

  • Datastore
  • Identity Platform

Use a calculadora de preços para gerar uma estimativa de custo baseada na projeção de uso. Novos usuários do Google Cloud podem estar qualificados para uma avaliação gratuita.

Antes de começar

  1. Instale Git, Python 2.7 (ambos em inglês) e virtualenv. Para mais informações sobre como configurar o ambiente de desenvolvimento do Python, como instalar a versão mais recente, consulte Como configurar um ambiente de desenvolvimento do Python para o Google Cloud.
  2. Faça login na sua conta do Google.

    Se você ainda não tiver uma, inscreva-se.

  3. No Console do Google Cloud, na página do seletor de projetos, selecione ou crie um projeto do Google Cloud.

    Acessar a página do seletor de projetos

  4. Instale e inicialize o SDK do Cloud..

Se você já tiver instalado e inicializado o SDK em um projeto diferente, defina o projeto gcloud como o ID do projeto do App Engine que você está usando para o Firenotes. Em Como gerenciar configurações do SDK do Cloud, consulte comandos específicos para atualizar um projeto com a ferramenta gcloud.

Como clonar o app de amostra

Para fazer o download do exemplo na máquina local:

  1. Clone o repositório de aplicativos de amostra na máquina local:

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

    Também é possível fazer o download do exemplo como um arquivo zip e extraí-lo.

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

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

Como adicionar a interface do usuário

Para configurar a FirebaseUI para o Identity Platform e ativar provedores de identidade, siga estas instruções:

  1. Adicione o Identity Platform ao aplicativo seguindo estas etapas:

    1. Acesse o Console do Cloud.
      Acessar o Console do Cloud
    2. Selecione o projeto do Google Cloud que você quer usar:
      • Se você tiver um projeto, selecione-o na lista suspensa Selecionar organização na parte superior da página.
      • Se você não tiver um projeto do Google Cloud, crie um novo no Console do Cloud.
    3. No Console do Cloud, acesse a página do Identity Platform no Marketplace.
      Acessar a página do Identity Platform no Marketplace
    4. Na página do Identity Platform, clique em Ativar a identidade do cliente.
    5. Acesse a página "Usuários" da identidade do cliente no Console do Cloud.
      Acessar a página "Usuários"
    6. No canto superior direito, clique em Detalhes da configuração do aplicativo.
    7. Copie os detalhes de configuração no aplicativo da Web.

      // 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 arquivo backend/app.yaml e insira o ID do projeto do Google Cloud nas variáveis de ambiente:

    runtime: python27
    api_version: 1
    threadsafe: true
    service: backend
    
    handlers:
    - url: /.*
      script: main.app
    
    env_variables:
      GAE_USE_SOCKETS_HTTPLIB : 'true'
    
  3. No arquivo frontend/main.js, configure o widget de login do FirebaseUI selecionando quais provedores você quer oferecer aos usuários.

      // 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);
      }
    
      // 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));
          });
        });
      }
    
      // Sign out a user
      var signOutBtn =$('#sign-out');
      signOutBtn.click(function(event) {
        event.preventDefault();
    
        firebase.auth().signOut().then(function() {
          console.log("Sign out successful");
        }, function(error) {
          console.log(error);
        });
      });
    
      // Save a note to the backend
      var saveNoteBtn = $('#add-note');
      saveNoteBtn.click(function(event) {
        event.preventDefault();
    
        var noteField = $('#note-content');
        var note = noteField.val();
        noteField.val("");
    
        /* Send note data to backend, storing in database with existing data
        associated with userIdToken */
        $.ajax(backendHostUrl + '/notes', {
          headers: {
            'Authorization': 'Bearer ' + userIdToken
          },
          method: 'POST',
          data: JSON.stringify({'message': note}),
          contentType : 'application/json'
        }).then(function(){
          // Refresh notebook display.
          fetchNotes();
        });
    
      });
    
      configureFirebaseLogin();
      configureFirebaseLoginWidget();
    
    });
    
  4. No Console do Cloud, ative os provedores que você escolheu manter:

    1. Acesse a página "Provedores" da identidade do cliente no Console do Cloud.
      Acessar a página "Provedores"
    2. Clique em Adicionar um provedor.
    3. Na lista suspensa Selecionar um provedor, escolha os provedores que você quer usar.
    4. Ao lado de Ativado, clique no botão para ativar o provedor.
      • Para Provedores de identidade de terceiros, insira o ID e o secret do provedor encontrados no site para desenvolvedores do provedor. A documentação do Firebase contém instruções específicas nas seções "Antes de começar" dos guias para Facebook, Twitter e GitHub.
      • Para integrações SAML e OIDC, consulte a configuração no seu IdP.
  5. Adicione seu domínio à lista de domínios autorizados no Identity Platform:

    1. Acesse a página "Configurações" da identidade do cliente no Console do Cloud.
      Acessar a página "Configurações"
    2. Em Domínios autorizados, clique em Adicionar domínio.
    3. Insira o domínio do seu app no seguinte formato:

      [PROJECT_ID].appspot.com
      

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

Como instalar dependências

  1. Navegue até o diretório backend e conclua a configuração do aplicativo:

    cd backend/
    
  2. Instale as dependências em um diretório lib do projeto:

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

Como executar o aplicativo localmente

Para executar o aplicativo 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 até o diretório raiz do aplicativo. Em seguida, inicie o servidor de desenvolvimento:

    dev_appserver.py frontend/app.yaml backend/app.yaml
    
  3. Acesse http://localhost:8080/ em um navegador da Web.

Como autenticar usuários no servidor

Agora que você configurou um projeto e inicializou um aplicativo para desenvolvimento, é possível percorrer o código para entender como recuperar e verificar os tokens de ID do Identity Platform no servidor.

Como conseguir um token de ID do Identity Platform

A primeira etapa na autenticação do lado do servidor é recuperar um token de acesso para verificar. As solicitações de autenticação são tratadas com o listener onAuthStateChanged() do 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 um usuário faz login, o método getToken() do Identity Platform no callback retorna um token de ID do Identity Platform na forma de um JSON Web Token (JWT).

Como verificar tokens no servidor

Depois que um usuário faz login, o serviço de front-end busca qualquer anotação atual no notebook do usuário por meio de uma solicitação AJAX GET. Isso requer autorização para acessar os dados do usuário, de modo que o JWT seja enviado no cabeçalho Authorization da solicitação usando o 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 que o cliente possa acessar os dados do servidor, o servidor precisa verificar se o token está assinado pelo Identity Platform. É possível verificar esse token usando a Biblioteca de autenticação do Google para Python. Use a função verify_firebase_token da biblioteca de autenticação para verificar o token do portador e extrair as declarações:

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 provedor de identidade envia um conjunto diferente de declarações, mas cada um tem pelo menos uma declaração sub com um ID de usuário exclusivo e uma declaração que fornece algumas informações de perfil, como name ou email, que podem ser usadas para personalizar a experiência do usuário no seu aplicativo.

Como gerenciar dados do usuário no Datastore

Depois de autenticar um usuário, você precisa armazenar os dados correspondentes para que isso persista após o término de uma sessão de login. Nas seções a seguir, explicamos como armazenar uma anotação como uma entidade do Datastore e segregar entidades por ID de usuário.

Como criar entidades para armazenar dados de usuário

É possível criar uma entidade no Datastore declarando uma classe de modelo NDB com determinadas propriedades, como números inteiros ou strings. O Datastore indexa entidades por tipo. No caso do Firenotes, o tipo de cada entidade é Note. Para fins de consulta, cada Note é armazenado com um nome de chave, que é o ID de usuário conseguido da declaração sub na seção anterior.

No código a seguir, demonstramos como definir propriedades de uma entidade, com o método construtor para a classe de modelo quando a entidade é criada e por meio da atribuição de propriedades individuais após a criação:

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 gravar o Note recém-criado no Datastore, chame o método put() no objeto note.

Como recuperar dados de usuário

Para recuperar dados do usuário associados a um ID de usuário específico, use o método query() do NDB para pesquisar no banco de dados as anotações no mesmo grupo de entidades. As entidades no mesmo grupo ou caminho do ancestral têm o mesmo nome de chave, que, nesse caso, é o ID de usuário.

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

Com isso, é possível buscar os dados da consulta e exibir as anotações no cliente:

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

Como implantar o app

Você integrou o Identity Platform ao aplicativo do App Engine. Para ver o aplicativo em execução em um ambiente de produção real, siga estas etapas:

  1. Altere o URL do host de back-end em main.js para https://backend-dot-[PROJECT_ID].appspot.com. Substitua [PROJECT_ID] pelo ID do projeto:
  2. Implante o aplicativo usando a interface da linha de comando do SDK do Cloud:

    gcloud app deploy backend/index.yaml frontend/app.yaml backend/app.yaml
    
  3. Veja o aplicativo ao vivo em https://[PROJECT_ID].appspot.com.

Como limpar

Para evitar cobranças na sua conta do Google Cloud pelos recursos usados neste tutorial, exclua o projeto do App Engine:

Como excluir o projeto

O jeito mais fácil de evitar cobranças é excluindo o projeto que você criou para o tutorial.

Para excluir o projeto:

  1. No Console do Cloud, acesse a página Gerenciar recursos:

    Acessar "Gerenciar recursos"

  2. Na lista de projetos, selecione o projeto que você quer excluir e clique em Excluir .
  3. Na caixa de diálogo, digite o ID do projeto e clique em Encerrar para excluí-lo.

A seguir