Como autenticar desenvolvedores, funções e usuários finais

Por padrão, somente proprietários e editores de projetos podem criar, atualizar ou excluir funções. Para conceder a outros usuários ou grupos a capacidade de executar essas ações, é possível usar o Cloud Identity and Access Management (IAM) para conceder papéis a diferentes membros.

Da mesma forma, é possível conceder ou restringir a capacidade de invocar uma função. Esse comportamento é diferente para funções HTTP e funções em segundo plano:

Três casos de uso comum para autenticação são:

  • Proteger o acesso do desenvolvedor para que apenas usuários específicos possam invocar a função durante o teste.

  • Proteger o acesso de função a função para que apenas funções autorizadas possam invocar sua função.

  • Proteger o acesso do usuário final a um aplicativo de clientes de dispositivos móveis ou da Web.

Desenvolvedores

Além de ações administrativas, como criar, atualizar e excluir funções, os desenvolvedores querem testar as funções de maneira privada antes de liberá-las publicamente.

Ao usar curl ou ferramentas semelhantes, você deve tratá-las como solicitações de usuários finais e fornecer um token OAuth do Google no cabeçalho Authorization da solicitação. Por exemplo, é possível obter um token via gcloud da seguinte maneira:

curl https://REGION-PROJECT_ID.cloudfunctions.net/FUNCTION_NAME \
  -H "Authorization: bearer $(gcloud auth print-identity-token)"

Para que essa solicitação funcione, o papel atribuído ao desenvolvedor deve conter a permissão cloudfunctions.functions.invoke. Por padrão, os papéis de administrador e de desenvolvedor do Cloud Functions têm essa permissão. Consulte Papéis de IAM do Cloud Functions para obter a lista completa de papéis e as permissões associadas.

Por fim, recomendamos que você aloque o conjunto mínimo de permissões necessárias para desenvolver e usar suas funções. Certifique-se de que as políticas do IAM nas suas funções estão limitadas ao número mínimo de usuários e contas de serviço.

Função a função

Ao criar serviços que conectam várias funções, é recomendável garantir que cada função só possa fazer solicitações para determinadas funções. Por exemplo, se você tiver uma função login, ela deve ser capaz de acessar a função user profiles, mas não a função search.

Primeiro, você precisa configurar a função de recebimento para aceitar solicitações da função de chamada:

  1. Conceda o papel de Invocador do Cloud Functions (roles/cloudfunctions.invoker) à identidade da função de chamada na função de recebimento. Por padrão, essa identidade é PROJECT_ID@appspot.gserviceaccount.com.

Console

  1. Acesse o Console do Google Cloud:

    Ir para o Google Cloud Console

  2. Clique na caixa de seleção ao lado da função cujas permissões você quer modificar.

  3. Clique em Mostrar painel de informações no canto superior direito para exibir a guia Permissões, que exibe os papéis atribuídos à função.

  4. No campo Adicionar membros, insira a identidade de ambiente a de execução da função de chamada. Ela deve ser um e-mail da conta de serviço.

  5. Selecione o papel Cloud Functions > Invocador do Cloud Functionsno menu suspenso Selecionar um papel.

  6. Clique em Salvar.

GCloud

Use o comando gcloud functions add-iam-policy-binding:

gcloud functions add-iam-policy-binding RECEIVING_FUNCTION \
  --member='serviceAccount:CALLING_FUNCTION_IDENTITY' \
  --role='roles/cloudfunctions.invoker'

em que RECEIVING_FUNCTION é a função de recebimento e CALLING_FUNCTION_IDENTITY é a identidade da função de chamada.

Na função de chamada, é preciso:

  1. Criar um token de código OAuth assinado pelo Google com o público-alvo (aud) definido como a URL da função de recebimento.

  2. Incluir o token de código em um cabeçalho Authorization: Bearer ID_TOKEN na solicitação para a função.

Node.js

const {get} = require('axios');

// TODO(developer): set these values
const REGION = 'us-central1';
const PROJECT_ID = 'my-project-id';
const RECEIVING_FUNCTION = 'myFunction';

// Constants for setting up metadata server request
// See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
const functionURL = `https://${REGION}-${PROJECT_ID}.cloudfunctions.net/${RECEIVING_FUNCTION}`;
const metadataServerURL =
  'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
const tokenUrl = metadataServerURL + functionURL;

exports.callingFunction = async (req, res) => {
  // Fetch the token
  const tokenResponse = await get(tokenUrl, {
    headers: {
      'Metadata-Flavor': 'Google',
    },
  });
  const token = tokenResponse.data;

  // Provide the token in the request to the receiving function
  try {
    const functionResponse = await get(functionURL, {
      headers: {Authorization: `bearer ${token}`},
    });
    res.status(200).send(functionResponse.data);
  } catch (err) {
    console.error(err);
    res.status(500).send('An error occurred! See logs for more details.');
  }
};

Python

import requests

# TODO<developer>: set these values
REGION = 'us-central1'
PROJECT_ID = 'my-project'
RECEIVING_FUNCTION = 'my-function'

# Constants for setting up metadata server request
# See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
function_url = f'https://{REGION}-{PROJECT_ID}.cloudfunctions.net/{RECEIVING_FUNCTION}'
metadata_server_url = \
    'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience='
token_full_url = metadata_server_url + function_url
token_headers = {'Metadata-Flavor': 'Google'}

def calling_function(request):
    # Fetch the token
    token_response = requests.get(token_full_url, headers=token_headers)
    jwt = token_response.content

    # Provide the token in the request to the receiving function
    function_headers = {'Authorization': f'bearer {jwt}'}
    function_response = requests.get(function_url, headers=function_headers)

    return function_response.content

Go

import (
	"context"
	"fmt"
	"io"

	"google.golang.org/api/idtoken"
)

// callFunction makes a request to the provided functionURL with an
// authenticated client.
func callFunction(w io.Writer, functionURL string) error {
	// functionURL := "https://REGION-PROJECT.cloudfunctions.net/RECEIVING_FUNCTION"
	ctx := context.Background()

	// client is a http.Client that automatically adds an "Authorization" header
	// to any requests made.
	client, err := idtoken.NewClient(ctx, functionURL)
	if err != nil {
		return fmt.Errorf("idtoken.NewClient: %v", err)
	}

	resp, err := client.Get(functionURL)
	if err != nil {
		return fmt.Errorf("client.Get: %v", err)
	}
	defer resp.Body.Close()
	if _, err := io.Copy(w, resp.Body); err != nil {
		return fmt.Errorf("io.Copy: %v", err)
	}

	return nil
}

Java


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.BufferedWriter;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.time.Duration;

public class BearerTokenHttp implements HttpFunction {

  // TODO<developer> specify values for these environment variables
  private static String REGION = System.getenv("TARGET_REGION");
  private static String PROJECT_ID = System.getenv("GCP_PROJECT");
  private static String RECEIVING_FUNCTION_NAME = "myFunction";

  private static String receivingFunctionUrl = String.format(
      "https://%s-%s.cloudfunctions.net/%s", REGION, PROJECT_ID, RECEIVING_FUNCTION_NAME);
  private static String metadataTokenEndpoint =
      "http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=";

  private static HttpClient client =
      HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException, InterruptedException {

    // Set up metadata server request
    // See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
    java.net.http.HttpRequest tokenRequest = java.net.http.HttpRequest.newBuilder()
        .uri(URI.create(metadataTokenEndpoint + receivingFunctionUrl))
        .GET()
        .header("Metadata-Flavor", "Google")
        .build();

    // Fetch the bearer token
    java.net.http.HttpResponse<String> tokenReponse =
        client.send(tokenRequest, java.net.http.HttpResponse.BodyHandlers.ofString());
    String token = tokenReponse.body();

    // Pass the token along to receiving function
    java.net.http.HttpRequest functionRequest = java.net.http.HttpRequest.newBuilder()
        .uri(URI.create(receivingFunctionUrl))
        .GET()
        .header("Authorization", "Bearer " + token)
        .build();
    java.net.http.HttpResponse<String> functionResponse =
        client.send(functionRequest, java.net.http.HttpResponse.BodyHandlers.ofString());

    // Write the results to the output:
    BufferedWriter writer = response.getWriter();
    writer.write(functionResponse.body());
  }
}

De serviço a função

Se você invocar uma função de uma instância de computador que não tem acesso a metadados (por exemplo, seu próprio servidor), é necessário gerar manualmente o token adequado:

  1. Autoassine um JWT de conta de serviço com a declaração target_audience definida como a URL da função de recebimento.

  2. Troque o JWT autoassinado por um token de código assinado pelo Google, que deve ter a reivindicação aud definida como a URL acima.

  3. Incluir o token de código em um cabeçalho Authorization: Bearer ID_TOKEN na solicitação para a função.

Os documentos do Cloud IAP têm códigos de amostra para demonstrar essa função.

Usuários finais

A maioria dos aplicativos processa solicitações de usuários finais. Essa é uma prática recomendada para restringir o acesso apenas a usuários finais permitidos. Para conseguir isso, é possível integrar o Login do Google e conceder aos usuários o papel de IAM roles/cloudfunctions.invoker ou implementar o Firebase Authentication e validar manualmente as credenciais.

Login do Google

Primeiro, ative o Login do Google no projeto:

  1. Crie um ID do cliente OAuth 2.0 para seu aplicativo no mesmo projeto da função que você quer proteger:
    1. Acesse a página Credenciais.

      Acesse a página Credenciais

    2. Selecione o projeto com a função que você quer proteger.
    3. Clique em Criar credenciais e selecione ID do cliente OAuth.
      1. Talvez você será solicitado a configurar sua tela de consentimento do OAuth antes de criar um ID de cliente. Se necessário, faça isso para continuar.
    4. Selecione o Tipo de aplicativo para que você quer criar credenciais.
    5. Adicione um Nome e Restrições, se apropriado, e clique em Criar.
  2. Implante novamente a função que você quer proteger. Isso garante que o ID correto do cliente seja definido na função.

Se houver vários IDs de cliente OAuth (por exemplo, um para Android, um para iOS e um para Web), implante novamente as funções depois de adicionar cada um deles para garantir que a função receba a alteração. Da mesma forma, se você excluir um ID de cliente, é preciso implantar de novo as funções para removê-lo e negar solicitações. Todos os IDs de cliente dentro de um projeto serão aceitos.

No app da Web ou para dispositivos móveis, é preciso:

  1. receber um token do código para o ID do cliente OAuth:
  2. incluir o token de código em um cabeçalho Authorization: Bearer ID_TOKEN na solicitação para a função.

O Cloud Functions valida o token de autenticação e permite ou rejeita a solicitação antes que a função seja iniciada. Se uma solicitação for rejeitada, você não será cobrado por ela.

Como acessar informações do perfil do usuário

Se você quer acessar as informações do perfil de usuário, retire o token do cabeçalho Authorization, decodifique o JSON Web Token e extraia o corpo.

O corpo do token de código deve conter as seguintes informações:

{
 // These six fields are included in all Google ID Tokens.
 "iss": "https://accounts.google.com",
 "sub": "110169484474386276334",
 "azp": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "aud": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "iat": "1433978353",
 "exp": "1433981953",

 // These seven fields are only included when the user has granted the "profile"
 // and "email" OAuth scopes to the application.
 "email": "testuser@gmail.com",
 "email_verified": "true",
 "name" : "Test User",
 "picture": "https://lh4.googleusercontent.com/-kYgzyAWpZzJ/ABCDEFGHI/AAAJKLMNOP/tIXL9Ir44LE/s99-c/photo.jpg",
 "given_name": "Test",
 "family_name": "User",
 "locale": "en"
}

Não é necessário validar o token, porque ele já foi validado pelo Cloud IAM.

Solução de problemas

Se as solicitações de usuários forem rejeitadas e você acreditar que elas devem ser permitidas, certifique-se de que os usuários tenham recebido o papel roles/cloudfunctions.invoker ou tenham a permissão cloudfunctions.functions.invoke. Saiba mais na referência do Cloud Functions IAM.

Aplicativos da Web, autenticação e CORS

Se você quer criar um aplicativo da Web protegido com o Login do Google e o Cloud Functions IAM, você provavelmente lidará com o CORS (Cross-Origin Resource Sharing). As solicitações de simulação do CORS são enviadas sem um cabeçalho Authorization. Portanto, elas são rejeitadas em todas as funções HTTP não públicas. Como há falha nas solicitações simuladas, a solicitação principal também falhará.

Para contornar esse problema, é possível hospedar seu aplicativo da web e suas funções no mesmo domínio para evitar solicitações simuladas de CORS. Caso contrário, torne suas funções públicas e processe o CORS e a autenticação no código de função.

Como alternativa, é possível implantar um proxy do Cloud Endpoints e ativar o CORS . Se você quiser recursos de autenticação, é possível ativar a validação do token de código do Google , que valida esses mesmos tokens de autenticação.

Firebase Authentication

Se você quiser autenticar usuários usando e-mail/senha, número de telefone, provedores de redes sociais como Facebook ou GitHub ou um mecanismo de autenticação personalizado, use o Firebase Authentication.

Primeiro, você precisará configurar o Firebase Authentication no seu projeto e função:

  1. Configure o Firebase Authentication no Console do Firebase.

    Acessar o Console do Firebase

  2. Importe o SDK Admin do Firebase apropriado e configure-o adequadamente.

  3. Adicione um middleware ao código para verificar os tokens do código do Firebase.

  4. Implante sua função publicamente.

Na Web ou app para dispositivo móvel, você precisa:

  1. Usar a biblioteca de cliente do Firebase Auth apropriada para receber um token do código:
  2. Incluir o token de código em um cabeçalho Authorization: Bearer ID_TOKEN na solicitação para a função.

Como alternativa, é possível implantar um proxy do Cloud Endpoints e ativar a validação do token de código do Firebase , validando esses mesmos tokens de autenticação.

Como acessar informações do perfil do usuário

Se você quiser acessar as informações do perfil do usuário, use o SDK Admin do Firebase para recuperar os dados do usuário.

Chaves de API

As chaves de API identificam o aplicativo cliente e o projeto do GCP chamando sua API, permitindo realizar uma simples autorização, bem como verificações de cota e limitação de taxa.

As chaves de API não são tão seguras quanto os outros métodos de autenticação listados pelos seguintes motivos:

  1. As chaves de API têm longa duração. Isso significa que se uma chave vazar, ela pode ser usada indefinidamente (ou pelo menos até que as chaves sejam giradas, o que exige que todos os clientes a atualizem).
  2. As chaves de API geralmente são armazenadas no lado do cliente, tornando-as vulneráveis à reutilização por partes maliciosas.

Se você optar por usar chaves de API para a cota e a limitação de taxa, recomendamos usá-las com tokens de autenticação.

Para configurar o acesso à chave de API, implante um proxy do Cloud Endpoints e ative configure o securityDefinitions para ativar a validação da chave de API.

Próximas etapas

Saiba como gerenciar o acesso às funções para as quais você está se autenticando.