Authentifier les utilisateurs

L'authentification permet au proxy ESP (Extensible Service Proxy) d'identifier les utilisateurs appelant les méthodes de votre service et de décider s'il les autorise à utiliser cette méthode. Cette page décrit le fonctionnement de l'authentification avec Cloud Endpoints pour les services gRPC, y compris la procédure de configuration d'ESP dans un service gRPC pour qu'il soit compatible avec les requêtes authentifiées et la procédure pour appeler des méthodes authentifiées à partir d'un client gRPC.

ESP est compatible avec plusieurs méthodes d'authentification, telles que Firebase, Auth0 et les jetons d'ID Google, qui peuvent toutes être paramétrées dans le cadre de votre configuration d'API gRPC. Dans chaque cas, le client doit fournir un jeton Web JSON (JWT) d'identification dans ses requêtes. ESP valide le jeton pour le compte de votre API. Vous n'avez donc pas besoin d'ajouter de code d'authentification spécial.

Bien que les exigences d’authentification et de clé d’API vous permettent de choisir qui peut appeler les méthodes de votre service, elles ne fournissent pas le même niveau de sécurité et fournissent des informations différentes au service appelé. Pour en savoir plus sur les différences entre les clés API et l'authentification, et sur leur utilisation respective, reportez-vous à la section Quand et pourquoi utiliser des clés API ?

Pour obtenir un exemple complet d'utilisation de l'authentification, consultez la section S'authentifier à l'aide d'un compte de service, qui ajoute une méthode d'authentification au service Bookstore de nos tutoriels.

Configurer l'authentification pour ESP

Configurez l'authentification Endpoints pour un service gRPC dans le fichier YAML de configuration du service gRPC, à l'aide de la section authentication. Spécifiez la méthode d'authentification et les détails de la source d'authentification dans providers, où :

  • la valeur id sert à identifier le fournisseur d'authentification défini dans la section rules : il s'agit généralement du nom de la méthode d'authentification, mais ce n'est pas obligatoire ;

  • La valeur issuer indique l'émetteur des jetons requis, et spécifie donc la méthode d'authentification.

  • la valeur jwks_uri est l'URI de la clé publique du fournisseur, utilisée pour valider les jetons. Certaines méthodes d'authentification ne nécessitent pas de spécifier cette valeur. C'est le cas y compris des jetons d'ID Google pour lesquels le proxy ESP obtient automatiquement cette information.

  • La valeur jwt_locations est utilisée pour définir les emplacements dans lesquels extraire le jeton JWT.

Vous pouvez définir plusieurs fournisseurs de sécurité dans le même fichier, mais chacun doit avoir une valeur issuer différente. Pour en savoir plus, consultez la documentation de l'option AuthProvider.

Spécifiez les méthodes d'API pour lesquelles vous souhaitez utiliser ces exigences d'authentification à l'aide de rules, comme décrit dans AuthenticationRule.

Les exemples suivants montrent comment configurer ESP dans un service gRPC pour certaines méthodes d'authentification compatibles :

Firebase

Pour assurer l'authentification Firebase :

authentication:
  providers:
  - id: firebase
    jwks_uri: https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken@system.gserviceaccount.com
    # Replace FIREBASE-PROJECT-ID with your Firebase project ID
    issuer: https://securetoken.google.com/FIREBASE-PROJECT-ID
    audiences: "FIREBASE-PROJECT-ID"
    # Optional.
    jwt_locations:
    # expect header "jwt-header-foo": "jwt-prefix-foo<TOKEN>"
    - header: "jwt-header-foo"
      value_prefix: "jwt-prefix-foo"
    - query: "jwt_query_bar"
  rules:
  - selector: "*"
    requirements:
      - provider_id: firebase

auth0

Pour assurer l'authentification Auth0 :

authentication:
  providers:
  - id: auth0_jwk
    # Replace YOUR-ACCOUNT-NAME with your service account's email address.
    issuer: https://YOUR-ACCOUNT-NAME.auth0.com/
    jwks_uri: "https://YOUR-ACCOUNT-NAME.auth0.com/.well-known/jwks.json"
    # Optional. Replace YOUR-CLIENT-ID with your client ID
    audiences: "YOUR-CLIENT-ID"
  rules:
  - selector: "*"
    requirements:
      - provider_id: auth0_jwk

Jeton d'ID Google

Pour assurer l'authentification à l'aide d'un jeton d'ID Google :

authentication:
  providers:
  - id: google_id_token
    # This "issuer" field has to match the field "iss" in the JWT token.
     # Sometime it is "accounts.google.com".
    issuer: https://accounts.google.com
    # Optional. Replace YOUR-CLIENT-ID with your client ID
    audiences: "YOUR-CLIENT-ID"
  rules:
  - selector: "*"
    requirements:
      - provider_id: google_id_token

Authentification personnalisée

Pour l'authentification personnalisée :

authentication:
  providers:
  - id: custom_auth_id
    # The value below should be unique
    issuer: issuer of the token
    jwks_uri: url to the public key
    # Optional. Replace YOUR-CLIENT-ID with your client ID
    audiences: "YOUR-CLIENT-ID"
 rules:
 - selector: "*"
   requirements:
     - provider_id: custom_auth_id

Pour Firebase Authentication, le champ audiences est obligatoire et doit correspondre à votre ID de projet Firebase. Pour toutes les autres méthodes d'authentification, il est facultatif. S'il n'est pas spécifié, ESP accepte tous les jetons JWT avec le nom du service de backend sous la forme https://SERVICE_NAME dans la revendication aud. Pour autoriser des ID client supplémentaires à accéder au service de backend, vous pouvez spécifier les ID client autorisés dans le champ audiences à l'aide de valeurs séparées par une virgule. ESP accepte ensuite les jetons JWT comportant dans la revendication aud les ID clients ajoutés à la liste blanche.

Appeler une méthode authentifiée à partir de gRPC

Si une méthode nécessite l'authentification, les clients gRPC doivent transmettre le jeton d'authentification sous forme de métadonnées avec leur appel de méthode, dans laquelle la clé doit être authorization et sa valeur Bearer <JWT_TOKEN>. Examinez les exemples de procédure à suivre ci-dessous lors de l'appel de l'exemple Bookstore dans Python, Node.js ou Java.

Python

def run(host, port, api_key, auth_token, timeout, use_tls, servername_override, ca_path):
    """Makes a basic ListShelves call against a gRPC Bookstore server."""

    if use_tls:
        with open(ca_path, 'rb') as f:
            creds = grpc.ssl_channel_credentials(f.read())
        channel_opts = ()
        if servername_override:
            channel_opts += ((
                        'grpc.ssl_target_name_override', servername_override,),)
        channel = grpc.secure_channel(f'{host}:{port}', creds, channel_opts)
    else:
        channel = grpc.insecure_channel(f'{host}:{port}')

    stub = bookstore_pb2_grpc.BookstoreStub(channel)
    metadata = []
    if api_key:
        metadata.append(('x-api-key', api_key))
    if auth_token:
        metadata.append(('authorization', 'Bearer ' + auth_token))
    shelves = stub.ListShelves(empty_pb2.Empty(), timeout, metadata=metadata)
    print(f'ListShelves: {shelves}')

Java

private static final class Interceptor implements ClientInterceptor {
  private final String apiKey;
  private final String authToken;

  private static Logger LOGGER = Logger.getLogger("InfoLogging");

  private static Metadata.Key<String> API_KEY_HEADER =
      Metadata.Key.of("x-api-key", Metadata.ASCII_STRING_MARSHALLER);
  private static Metadata.Key<String> AUTHORIZATION_HEADER =
      Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);

  public Interceptor(String apiKey, String authToken) {
    this.apiKey = apiKey;
    this.authToken = authToken;
  }

  @Override
  public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
      MethodDescriptor<ReqT,RespT> method, CallOptions callOptions, Channel next) {
    LOGGER.info("Intercepted " + method.getFullMethodName());
    ClientCall<ReqT, RespT> call = next.newCall(method, callOptions);

    call = new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(call) {
      @Override
      public void start(Listener<RespT> responseListener, Metadata headers) {
        if (apiKey != null && !apiKey.isEmpty()) {
          LOGGER.info("Attaching API Key: " + apiKey);
          headers.put(API_KEY_HEADER, apiKey);
        }
        if (authToken != null && !authToken.isEmpty()) {
          System.out.println("Attaching auth token");
          headers.put(AUTHORIZATION_HEADER, "Bearer " + authToken);
        }
        super.start(responseListener, headers);
      }
    };
    return call;
  }
}

Node.js

const makeGrpcRequest = (JWT_AUTH_TOKEN, API_KEY, HOST, GREETEE) => {
  // Uncomment these lines to set their values
  // const JWT_AUTH_TOKEN = 'YOUR_JWT_AUTH_TOKEN';
  // const API_KEY = 'YOUR_API_KEY';
  // const HOST = 'localhost:50051'; // The IP address of your endpoints host
  // const GREETEE = 'world';

  // Import required libraries
  const grpc = require('grpc');
  const path = require('path');

  // Load protobuf spec for an example API
  const PROTO_PATH = path.join(__dirname, '/protos/helloworld.proto');
  const protoObj = grpc.load(PROTO_PATH).helloworld;

  // Create a client for the protobuf spec
  const client = new protoObj.Greeter(HOST, grpc.credentials.createInsecure());

  // Build gRPC request
  const metadata = new grpc.Metadata();
  if (API_KEY) {
    metadata.add('x-api-key', API_KEY);
  } else if (JWT_AUTH_TOKEN) {
    metadata.add('authorization', `Bearer ${JWT_AUTH_TOKEN}`);
  }

  // Execute gRPC request
  client.sayHello({name: GREETEE}, metadata, (err, response) => {
    if (err) {
      console.error(err);
    }

    if (response) {
      console.log(response.message);
    }
  });
};

La façon dont le client obtient un fichier JWT valide à envoyer dépend de la méthode d'authentification.

Recevoir les résultats d'authentification dans votre API

ESP transfère généralement tous les en-têtes reçus. Cependant, il remplace l'en-tête Authorization d'origine lorsque l'adresse de backend est spécifiée par x-google-backend dans la spécification OpenAPI ou BackendRule dans la configuration du service gRPC.

ESP envoie le résultat de l'authentification dans le champ X-Endpoint-API-UserInfo à l'API backend. Nous vous recommandons d'utiliser cet en-tête plutôt que l'en-tête Authorization d'origine. Cet en-tête est une chaîne qui base64url encode un objet JSON. Le format d'objet JSON diffère entre ESPv2 et ESP. Pour ESPv2, l'objet JSON correspond exactement à la charge utile d'origine du jeton JWT. Pour ESP, l'objet JSON utilise des noms de champs différents et place la charge utile JWT d'origine sous le champ claims. Pour en savoir plus sur le format, consultez Gérer les jetons JWT dans le service de backend.

Étapes suivantes