Authentifier des développeurs, des fonctions et des utilisateurs finaux

Par défaut, seuls les propriétaires et les éditeurs de projets peuvent créer, mettre à jour ou supprimer des fonctions. Pour permettre à d'autres utilisateurs ou groupes d'effectuer ces actions, vous pouvez accorder des rôles aux différents membres à l'aide de Cloud IAM (Cloud Identity and Access Management).

De même, vous pouvez accorder ou limiter la possibilité d'appeler une fonction. Ce comportement diffère pour les fonctions HTTP et les fonctions d'arrière-plan :

Voici trois cas d'utilisation courants de l'authentification :

  • Sécuriser l'accès du développeur de sorte que seuls des utilisateurs spécifiques puissent appeler la fonction pendant les tests.

  • Sécuriser l'accès de fonction à fonction de sorte que seules les fonctions autorisées puissent appeler votre fonction.

  • Sécuriser l'accès des utilisateurs finaux à une application à partir de clients mobiles ou Web.

Développeurs

Outre les actions administratives telles que la création, la mise à jour et la suppression de fonctions, les développeurs souhaitent souvent tester des fonctions en privé avant de les lancer publiquement.

Lorsque vous utilisez curl ou des outils similaires, vous devez les traiter comme des requêtes d'utilisateurs finaux et fournir un jeton Google OAuth dans l'en-tête Authorization des requêtes. Par exemple, vous pouvez obtenir un jeton via gcloud comme suit :

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

Pour que cette requête fonctionne, le rôle attribué au développeur doit contenir l'autorisation cloudfunctions.functions.invoke. Par défaut, les rôles Administrateur Cloud Functions et Développeur Cloud Functions disposent de cette autorisation. Consultez la page Rôles IAM de Cloud Functions pour obtenir la liste complète des rôles et des autorisations associées.

Enfin, nous vous recommandons d'attribuer l'ensemble minimal d'autorisations requis pour développer et utiliser vos fonctions. Assurez-vous que les stratégies IAM associées à vos fonctions sont limitées au nombre minimal d'utilisateurs et de comptes de service.

Fonction à fonction

Lors de la création de services qui connectent plusieurs fonctions, il est recommandé de s'assurer que chaque fonction ne peut envoyer des requêtes qu'à certaines autres fonctions. Par exemple, si vous avez une fonction login, elle doit pouvoir accéder à la fonction user profiles, mais probablement pas à la fonction search.

Commencez par configurer la fonction de réception pour accepter les requêtes provenant de la fonction d'appel :

  1. Accordez le rôle Demandeur Cloud Functions (roles/cloudfunctions.invoker) à l'identité de la fonction d'appel sur la fonction de réception. Par défaut, cette identité est PROJECT_ID@appspot.gserviceaccount.com.

Console

  1. Accédez à Google Cloud Console :

    Accéder à Google Cloud Console

  2. Cochez la case à côté de la fonction dont vous souhaitez modifier les autorisations.

  3. Cliquez sur Afficher le panneau d'informations dans l'angle supérieur droit pour afficher l'onglet Autorisations, qui contient les rôles attribués à la fonction.

  4. Dans le champ Ajouter des membres, saisissez l'identité d'exécution de la fonction d'appel. Il doit s'agir d'une adresse e-mail de compte de service.

  5. Sélectionnez le rôle Cloud Functions > Demandeur Cloud Functions dans le menu déroulant Sélectionner un rôle.

  6. Cliquez sur Enregistrer.

gcloud

Utilisez la commande gcloud functions add-iam-policy-binding :

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

RECEIVING_FUNCTION est la fonction de réception et CALLING_FUNCTION_IDENTITY est l'identité de la fonction d'appel.

Dans la fonction d'appel, vous devez :

  1. créer un jeton d'ID OAuth signé par Google dont l'audience (aud) est définie sur l'URL de la fonction de réception ;

  2. inclure le jeton d'ID dans un en-tête Authorization: Bearer ID_TOKEN de la requête destinée à la fonction.

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

Service à fonction

Si vous appelez une fonction depuis une instance de calcul qui n'a pas accès aux métadonnées de calcul (par exemple, votre propre serveur), vous devez générer manuellement le jeton approprié :

  1. Autosignez un compte de service JWT avec la revendication target_audience définie sur l'URL de la fonction de réception.

  2. Remplacez le JWT autosigné par un jeton d'ID signé par Google, dont la revendication aud doit correspondre à l'URL ci-dessus.

  3. Incluez le jeton d'ID dans un en-tête Authorization: Bearer ID_TOKEN de la requête destinée à la fonction.

La documentation de Cloud IAP inclut un exemple de code pour illustrer cette fonctionnalité.

Utilisateurs finaux

La plupart des applications gèrent les requêtes des utilisateurs finaux, et il est recommandé de limiter l'accès uniquement aux utilisateurs finaux autorisés. Pour ce faire, vous pouvez intégrer Google Sign-In et attribuer le rôle IAM de demandeur Cloud Functions (roles/cloudfunctions.invoker) aux utilisateurs, ou mettre en œuvre Firebase Authentication et valider manuellement leurs identifiants.

Google Sign-In

Commencez par activer Google Sign-In dans votre projet :

  1. Créez un ID client OAuth 2.0 pour votre application dans le même projet que la fonction à sécuriser :
    1. Accédez à la page Identifiants.

      Accéder à la page Identifiants

    2. Sélectionnez le projet avec la fonction que vous souhaitez sécuriser.
    3. Cliquez sur Créer des identifiants, puis sélectionnez ID client OAuth.
      1. Vous devrez peut-être configurer votre écran d'autorisation OAuth avant de créer un ID client. Si nécessaire, faites-le pour pouvoir continuer.
    4. Sélectionnez le type d'application pour lequel vous souhaitez créer des identifiants.
    5. Ajoutez un nom et des restrictions, le cas échéant. Cliquez ensuite sur Créer.
  2. Redéployez la fonction que vous souhaitez sécuriser pour garantir que l'ID client correct est défini sur la fonction.

Si vous avez plusieurs ID clients OAuth (par exemple, un pour Android, un pour iOS et un pour le Web), vous devez redéployer votre fonction chaque fois que vous en ajoutez un pour vous assurer que la fonction récupère les modifications. De même, si vous supprimez un ID client, vous devez redéployer votre fonction pour supprimer cet ID client et refuser les requêtes. Tous les ID clients d'un projet seront acceptés.

Dans votre application Web ou mobile, vous devez :

  1. Obtenez un jeton d'ID pour l'ID client OAuth :
  2. inclure le jeton d'ID dans un en-tête Authorization: Bearer ID_TOKEN de la requête destinée à la fonction.

Les fonctions Cloud Functions valideront le jeton d'authentification et autoriseront la requête, ou refuseront la requête avant le démarrage de la fonction. Si une requête est refusée, vous ne serez pas facturé pour celle-ci.

Obtenir des informations sur un profil utilisateur

Si vous souhaitez accéder aux informations d'un profil utilisateur, vous pouvez extraire le jeton de l'en-tête Authorization, décoder le jeton Web JSON et extraire le corps du jeton.

Le corps du jeton d'ID doit contenir les informations suivantes :

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

Il n'est pas nécessaire de valider le jeton, car il a déjà été validé par IAM.

Dépannage

Si des requêtes utilisateur sont refusées alors qu'elles devraient être autorisées, assurez-vous que le rôle roles/cloudfunctions.invoker a bien été attribué aux utilisateurs ou que ceux-ci disposent de l'autorisation cloudfunctions.functions.invoke. Pour en savoir plus à ce sujet, consultez la documentation de référence sur Cloud Functions IAM.

Applications Web, authentification et CORS

Si vous souhaitez créer une application Web sécurisée avec Google Sign-in et Cloud Functions IAM, vous devrez probablement gérer le partage de ressources entre origines multiples (CORS). Les requêtes CORS préliminaires sont envoyées sans en-tête Authorization. Elles seront donc refusées pour toutes les fonctions HTTP non publiques. Comme les requêtes préliminaires échouent, la requête principale échouera également.

Pour contourner ce problème, vous pouvez héberger vos fonctions et votre application Web sur le même domaine afin d'éviter les requêtes CORS préliminaires. Sinon, vous devez rendre vos fonctions publiques et gérer le CORS et l'authentification dans le code de la fonction.

Vous pouvez également déployer un proxy Cloud Endpoints et activer le CORS. Si vous souhaitez ajouter des fonctionnalités d'authentification, vous pouvez également activer la validation des jetons d'identification Google, ce qui validera ces mêmes jetons d'authentification.

Firebase Authentication

Si vous souhaitez authentifier les utilisateurs à l'aide d'une combinaison adresse e-mail/mot de passe, d'un numéro de téléphone, d'un compte de réseau social tel que Facebook ou GitHub, ou d'un mécanisme d'authentification personnalisé, vous pouvez utiliser Firebase Authentication.

Commencez par configurer Firebase Authentication dans votre projet et votre fonction :

  1. Configurez Firebase Authentication dans la console Firebase.

    Accéder à la console Firebase

  2. Importez le SDK Admin Firebase approprié et configurez-le correctement.

  3. Ajoutez un middleware à votre code pour vérifier les jetons d'ID Firebase.

  4. Déployez votre fonction publiquement.

Dans votre application Web ou mobile, vous devez :

  1. Utilisez la bibliothèque cliente Firebase Authentication appropriée pour obtenir un jeton d'ID :
  2. Incluez le jeton d'ID dans un en-tête Authorization: Bearer ID_TOKEN de la requête destinée à la fonction.

Vous pouvez également déployer un proxy Cloud Endpoints et activer la validation des jetons d'identification Firebase, ce qui validera ces mêmes jetons d'authentification.

Obtenir des informations sur un profil utilisateur

Si vous souhaitez accéder aux informations d'un profil utilisateur, vous pouvez utiliser le SDK Admin Firebase pour récupérer des données utilisateur.

Clés API

Les clés API identifient l'application cliente et le projet GCP qui appellent votre API, ce qui vous permet d'effectuer une autorisation simple, ainsi que des contrôles de quotas et de limitation de débit.

Les clés API ne sont pas aussi sécurisées que les autres méthodes d'authentification répertoriées ici pour les raisons suivantes :

  1. Les clés API ont une longue durée de vie. Si une fuite se produit, les clés peuvent être utilisées indéfiniment (ou au moins jusqu'à leur rotation, ce qui oblige tous les clients à les mettre à jour).
  2. Les clés API étant souvent stockées côté client, elles sont susceptibles d'être réutilisées par des tiers malveillants.

Si vous choisissez d'utiliser des clés API pour le contrôle des quotas et la limitation du débit, nous vous recommandons de les associer à des jetons d'authentification.

Pour configurer l'accès aux clés API, déployez un proxy Cloud Endpoints et activez l'option Configurer les paramètres securityDefinitions pour activer la validation des clés API.

Étapes suivantes

Découvrez comment gérer l'accès aux fonctions sur lesquelles vous vous authentifiez.