Autentica usuarios

La autenticación le permite al proxy de servicio extensible (ESP) identificar a los usuarios que llaman a los métodos del servicio y luego, basándose en esto, decidir si les permite usar ese método (autorización). En esta página, se describe cómo funciona la autenticación con los servicios de Cloud Endpoints en gRPC, incluso la forma de configurar el ESP en un servicio de gRPC para admitir solicitudes autenticadas y cómo llamar a métodos autenticados desde un cliente gRPC.

El ESP admite varios métodos de autenticación, entre ellos, Firebase, Auth0, y tokens de ID de Google, los cuales pueden ser parte de la configuración de la API de gRPC. En cada caso, el cliente debe proporcionar un token web JSON (JWT) de identificación en sus solicitudes. El ESP valida el token en nombre de la API, por lo que no necesitas agregar ningún código de autenticación especial.

Si bien los requisitos de autenticación y de la clave de API te permiten restringir quién puede llamar a los métodos de tu servicio, no proporcionan el mismo nivel de seguridad y envían información diferente al servicio llamado. Puedes encontrar más información sobre las diferencias entre claves de API y la autenticación y el momento apropiado para usar cada esquema en Cuándo y por qué usar claves de API.

Para ver un ejemplo funcional completo de uso de la autenticación, consulta Cómo autenticar con una cuenta de servicio, que agrega autenticación al servicio de librería de nuestros Instructivos.

Cómo configurar la autenticación para el ESP

Puedes configurar la autenticación para un servicio de Endpoints para gRPC en su archivo YAML de configuración de servicio de gRPC mediante la sección authentication. Debes especificar el método de autenticación y los detalles de la fuente de autenticación, como providers, en los que se muestra lo siguiente:

  • El valor id se usa para identificar el proveedor de autenticación cuando se usa en rules: esto suele usar el nombre del método de autenticación, pero no es necesario que lo tenga.

  • El valor issuer es la entidad emisora de los tokens necesarios, por lo cual especifica el método de autenticación.

  • El valor jwks_uri es el URI de la clave pública del proveedor, que se usa para validar los tokens. Algunos métodos de autenticación no necesitan que especifiques esto, como los tokens de ID de Google, caso en el cual el ESP obtiene la información de forma automática.

  • jwt_locations se usa para definir las ubicaciones y extraer el JWT.

Se pueden definir varios proveedores de seguridad en el mismo archivo, pero cada uno debe tener un issuer diferente. Consulta AuthProvider para obtener más información.

Especifica los métodos de API que deseas que usen estos requisitos de autenticación mediante rules, como se describe en AuthenticationRule.

Los ejemplos siguientes muestran cómo configurar el ESP en un servicio de gRPC para algunos métodos de autenticación admitidos:

Firebase

Para que admita la autenticación de 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

Para que admita la autenticación de 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

Token de ID de Google

Para que admita la autenticación con un token de ID de 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

De forma personalizada

Para que admita la autenticación personalizada:

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

En Firebase Authentication, el campo audiences es obligatorio y debe ser el ID de tu proyecto de Firebase. Para todos los demás métodos de autenticación, es opcional. Si no se especifica, el ESP acepta todos los JWT con nombre de servicio de backend con formato https://SERVICE_NAME en la reclamación aud. Si deseas permitir que ID de cliente adicionales puedan acceder al servicio de backend, puedes especificar los ID de cliente autorizados en el campo audiences mediante valores separados por comas. Entonces, el ESP acepta los JWT con los ID de cliente incluidos en la lista en la reclamación aud.

Cómo llamar un método autenticado de gRPC

Si un método requiere autenticación, los clientes de gRPC deben pasar el token de autenticación como metadatos con su llamada de método, authorization y el valor es Bearer <JWT_TOKEN>. Ve un ejemplo sobre cómo hacer esto cuando se llama el ejemplo de librería en Python, Node.js o 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 forma en que el cliente obtiene un JWT válido para enviar dependerá del método de autenticación.

Cómo recibir resultados de autenticación en tu API

Por lo general, el ESP reenvía todos los encabezados que recibe. Sin embargo, anula el encabezado original Authorization cuando la dirección de backend se especifique mediante x-google-backend en la especificación de OpenAPI o BackendRule en la configuración del servicio de gRPC.

El ESP enviará el resultado de la autenticación en X-Endpoint-API-UserInfo a la API de backend. Recomendamos usar este encabezado en lugar del encabezado original Authorization. Este encabezado es una string que base64url codifica un objeto JSON. El formato de objeto JSON difiere entre ESPv2 y ESP. Para el ESPv2, el objeto JSON es exactamente la carga útil original de JWT. Para el ESP, el objeto JSON usa diferentes nombres de campo y coloca la carga útil de JWT original en el campo claims. Consulta Maneja JWT en el servicio de backend para obtener más información sobre el formato.

¿Qué sigue?