Autentica desarrolladores, funciones y usuarios finales

Según la configuración predeterminada, solo los propietarios de proyecto y los editores pueden crear, actualizar o borrar funciones. A fin de otorgar a otros usuarios o grupos la capacidad de realizar estas acciones, puedes usar la administración de identidades y accesos (IAM) para otorgar funciones a diferentes miembros.

De manera similar, puedes asignar o restringir la capacidad de invocar una función. Este comportamiento difiere para las funciones de HTTP y las funciones en segundo plano:

A continuación, se presentan tres casos prácticos comunes de autenticación:

  • Asegura el acceso del desarrollador para que solo usuarios específicos puedan invocar la función durante la prueba.

  • Asegura el acceso de función a función para que solo las funciones autorizadas puedan invocar tu función.

  • Asegura el acceso del usuario final a una aplicación desde clientes móviles o web.

Desarrolladores

Además de las acciones administrativas como crear, actualizar y borrar funciones, a menudo, los desarrolladores quieren probar funciones de forma privada antes de lanzarlas de forma pública.

Cuando usas curl o herramientas similares, debes tratarlas como solicitudes de usuario final y proporcionar un token de Google OAuth en el encabezado Authorization de la solicitud. Por ejemplo, puedes obtener un token por medio de gcloud de la siguiente manera:

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

Para que esta solicitud funcione, la función asignada al desarrollador debe contener el permiso cloudfunctions.functions.invoke. Según la configuración predeterminada, las funciones de Administrador y de Desarrollador de Cloud Functions tienen este permiso. Consulta Funciones de IAM de Cloud Functions para ver la lista completa de las funciones y los permisos asociados con estas.

Por último, te recomendamos otorgar el conjunto de permisos necesarios mínimos para el desarrollo y el uso de tus funciones. Asegúrate de que las políticas de IAM en tus funciones estén limitadas a la cantidad mínima de usuarios y cuentas de servicio.

Función a función

Cuando compilas servicios que conectan varias funciones, es recomendable que te asegures de que cada función solo pueda hacer solicitudes a ciertas funciones. Por ejemplo, si tienes una función login, esta debería poder acceder a la función user profiles, pero probablemente no debería poder acceder a la función search.

Primero, será necesario que configures la función receptora para que acepte solicitudes de la función que llama. Para ello, sigue estos pasos:

  1. Otorga la función de invocador de Cloud Functions (roles/cloudfunctions.invoker) a la identidad de función que llama en la función receptora. Según la configuración predeterminada, esa identidad es PROJECT_ID@appspot.gserviceaccount.com.

Console

  1. Ve a Google Cloud Console:

    Ir a Google Cloud Console

  2. Haz clic en la casilla de verificación que se encuentra junto a la función cuyos permisos deseas modificar.

  3. Haz clic en Mostrar panel de información en la esquina superior derecha para que aparezca la pestaña Permisos, que muestra los permisos asignados a la función.

  4. En el campo Agregar miembros, ingresa la identidad del entorno de ejecución de la función que llama. Debería ser el correo electrónico de una cuenta de servicio.

  5. Selecciona Cloud Functions > Invocador de Cloud Functions en el menú desplegable Seleccionar una función.

  6. Haz clic en Guardar.

GCloud

Usa el comando gcloud functions add-iam-policy-binding:

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

donde RECEIVING_FUNCTION es la función receptora y CALLING_FUNCTION_IDENTITY es la identidad de la función que llama.

En la función que llama, será necesario que hagas lo siguiente:

  1. Crea un token de ID de OAuth firmado por Google con el público (aud) configurado en la URL de la función receptora.

  2. Incluye el token de ID en un encabezado Authorization: Bearer ID_TOKEN en la solicitud de la función.

Node.js

const fetch = require('node-fetch');

// 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 fetch(tokenUrl, {
    headers: {
      'Metadata-Flavor': 'Google',
    },
  });
  const token = await tokenResponse.text();

  // Provide the token in the request to the receiving function
  try {
    const functionResponse = await fetch(functionURL, {
      headers: {Authorization: `bearer ${token}`},
    });
    res.status(200).send(await functionResponse.text());
  } 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.text

    # 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.text

Go


import (
	"context"
	"fmt"
	"io"

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

// makeGetRequest makes a request to the provided targetURL with an authenticated client.
func makeGetRequest(w io.Writer, targetURL string) error {
	// functionURL := "https://TARGET_URL"
	ctx := context.Background()

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

	resp, err := client.Get(targetURL)
	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());
  }
}

Servicio a función

Si invocas una función desde una instancia de procesamiento que no tiene acceso a los metadatos de procesamiento (p. ej., tu propio servidor), tendrás que generar manualmente el token correspondiente con estos pasos:

  1. Autofirma un JWT de cuenta de servicio con la reclamación target_audience configurada en la URL de la función receptora.

  2. Intercambia el JWT autofirmado por un token de ID firmado por Google, que debería tener la reclamación aud configurada en la URL anterior.

  3. Incluye el token de ID en un encabezado Authorization: Bearer ID_TOKEN en la solicitud a la función.

Los documentos de Cloud IAP tienen un código de muestra para demostrar esta funcionalidad.

Usuarios finales

La mayoría de las aplicaciones administran solicitudes de usuarios finales y una práctica recomendada es restringir el acceso solo a los usuarios finales con permiso. A fin de lograr lo anterior, puedes integrar el Acceso con Google y otorgar a los usuarios la función de IAM de invocador de Cloud Functions (roles/cloudfunctions.invoker) o implementar Firebase Authentication y validar sus credenciales de forma manual.

Acceso con Google

Primero, será necesario habilitar el Acceso con Google en tu proyecto de la siguiente manera:

  1. Crea un ID de cliente de OAuth 2.0 para tu app en el mismo proyecto que la función que quieres proteger:
    1. Ve a la página Credenciales.

      Ir a la página Credenciales

    2. Selecciona el proyecto con la función que quieres asegurar.
    3. Haz clic en Crear credenciales y selecciona ID de cliente de OAuth.
      1. Es posible que se te pida configurar tu propia pantalla de consentimiento para OAuth antes de crear un ID de cliente. Si es necesario, hazlo para continuar.
    4. Selecciona el Tipo de aplicación para la que quieres crear credenciales.
    5. Agrega un Nombre y Restricciones si corresponde y, luego, haz clic en Crear.
  2. Vuelve a implementar la función que quieres proteger. Esto garantizará que se configure el ID de cliente correcto en la función.

Si tienes varios ID de cliente de OAuth (por ejemplo, uno por cada plataforma: Android, iOS y la Web), debes volver a implementar tus funciones, después de agregar cada una para asegurarte de que la función capte el cambio. De forma similar, si borras un ID de cliente, debes volver a implementar tus funciones para quitarlo y rechazar sus solicitudes. Se aceptarán todos los ID de cliente dentro de un proyecto.

En tu app web o para dispositivos móviles, será necesario que hagas lo siguiente:

  1. Obtén un token de ID para el ID de cliente de OAuth:
  2. Incluye el token de ID en un encabezado de Authorization: Bearer ID_TOKEN en la solicitud de la función.

Cloud Functions validará el token de autenticación y permitirá la solicitud, o la rechazará antes de que la función se inicie. Si se rechaza una solicitud, no se te cobrará por eso.

Obtén información del perfil del usuario

Si quieres acceder a la información del perfil del usuario, puedes extraer el token del encabezado Authorization, decodificar el token web JSON y extraer el cuerpo.

El cuerpo del token de ID debe contener la siguiente información:

{
 // 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"
}

No es necesario validar el token, dado que IAM ya lo hizo.

Soluciona problemas

Si se rechazan las solicitudes de los usuarios y consideras que deben aceptarse, asegúrate de que a los usuarios se les haya otorgado la función roles/cloudfunctions.invoker o de que tengan el permiso cloudfunctions.functions.invoke. Obtén más información sobre lo anterior en la referencia de IAM de Cloud Functions.

Apps web, autenticación y CORS

Si quieres compilar una app web que está protegida por la IAM de Cloud Functions y el Acceso con Google, es probable que debas trabajar con el uso compartido de recursos entre dominios (CORS). Las solicitudes de comprobación previa de CORS se envían sin un encabezado de Authorization, por lo que se rechazarán en todas las funciones de HTTP no públicas. Como las solicitudes de verificación previa fallan, la solicitud principal también lo hará.

Para sortear lo anterior, puedes alojar tu app web y las funciones en el mismo dominio a fin de evitar las solicitudes de comprobación previa de CORS. De lo contrario, debes hacer públicas tus funciones y administrar CORS y la autenticación en el código de la función.

También puedes implementar un proxy de Cloud Endpoints y habilitar CORS. Si deseas contar con capacidades de autenticación, también puedes habilitar la validación del token de ID de Google, que validará estos mismos tokens de autenticación.

Firebase Authentication

Si quieres autenticar usuarios con correo electrónico/contraseña, número de teléfono, proveedores de redes sociales como Facebook o GitHub, o con un mecanismo de autenticación personalizado puedes usar Firebase Authentication.

Primero, deberás configurar Firebase Authentication en tu proyecto y función con estos pasos:

  1. Configura Firebase Authentication en Firebase Console.

    Ir a Firebase console

  2. Importa el SDK de Firebase Admin correspondiente y configúralo correctamente.

  3. Agrega middleware a tu código para verificar los tokens de ID de Firebase.

  4. Implementa tu función públicamente.

En tu app web o móvil, es necesario que hagas lo siguiente:

  1. Usa la biblioteca cliente apropiada de Firebase Auth para obtener un token de ID:
  2. Incluye el token de ID en un encabezado Authorization: Bearer ID_TOKEN en la solicitud a la función.

También puedes implementar un proxy de Cloud Endpoints y habilitar la validación del token de ID de Firebase, que validará estos mismos tokens de autenticación.

Obtén información del perfil del usuario

Si quieres acceder a la información del perfil del usuario, puedes usar el SDK de Firebase Admin para recuperar los datos del usuario.

Claves de API

Las claves de API identifican la aplicación cliente y el proyecto de GCP que llaman a tu API, lo que te permite realizar una autorización simple, así como verificaciones de cuotas y límites de frecuencia.

Las claves de API no son tan seguras como los otros métodos de autenticación que se enumeran aquí por los siguientes motivos:

  1. Las claves de API son de larga duración. Esto significa que, aunque se filtren, se pueden usar indefinidamente (o al menos hasta que se roten las claves, lo que requiere que todos los clientes actualicen la clave).
  2. Las claves de API suelen almacenarse del lado del cliente, lo que las hace vulnerables a ser reutilizadas por terceros malintencionados.

Si eliges usar claves de API para el límite de frecuencia y cuotas, te recomendamos que las uses junto con tokens de autenticación.

A fin de configurar el acceso a las claves de API, implementa un proxy de Cloud Endpoints y configura tu securityDefinitions para habilitar la validación de las claves de API.

Próximos pasos

Aprende cómo administrar el acceso a las funciones que se autentican.