Autenticar usuarios

La autenticación permite que Extensible Service Proxy (ESP) identifique a los usuarios que llaman a los métodos de tu servicio y, en función de esto, decida si les permite usar ese método (autorización). En esta página se describe cómo funciona la autenticación con Cloud Endpoints para servicios gRPC, incluido cómo configurar ESP en un servicio gRPC para admitir solicitudes autenticadas y cómo llamar a métodos autenticados desde un cliente gRPC.

ESP admite varios métodos de autenticación, como Firebase, Auth0 y tokens de ID de Google, que se pueden configurar como parte de la configuración de la API gRPC. En cada caso, el cliente debe proporcionar un JSON Web Token (JWT) identificativo en sus solicitudes. El ESP valida el token en nombre de tu API, por lo que no tienes que añadir ningún código de autenticación especial.

Aunque los requisitos de autenticación y de 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 ofrecen información diferente al servicio al que se llama. Puedes consultar más información sobre las diferencias entre las claves de API y la autenticación, así como cuándo es adecuado usar cada esquema, en el artículo Por qué y cuándo utilizar claves de API.

Para ver un ejemplo práctico completo que utilice la autenticación, consulta Autenticar con una cuenta de servicio, que añade autenticación al servicio Bookstore de nuestros tutoriales.

Configurar la autenticación para ESP

Para configurar la autenticación de un servicio de Endpoints para gRPC, debes usar la sección authentication en el archivo YAML de configuración del servicio gRPC. Especifica el método de autenticación y los detalles de la fuente de autenticación como providers, donde:

  • El valor id se usa para identificar al proveedor de autenticación cuando se usa en rules: normalmente, se usa el nombre del método de autenticación, pero no es obligatorio.

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

  • El valor de jwks_uri es el URI de la clave pública del proveedor, que se usa para validar tokens. En algunos métodos de autenticación no es necesario especificarlo, como en los tokens de ID de Google, en los que el ESP obtiene la información automáticamente.

  • El jwt_locations se usa para definir las ubicaciones de las que se extraerá el JWT.

Puedes 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.

Para especificar los métodos de la API que quieres usar con estos requisitos de autenticación, utiliza rules, tal como se describe en AuthenticationRule.

En los siguientes ejemplos se muestra cómo configurar ESP en un servicio gRPC para algunos métodos de autenticación admitidos:

firebase

Para admitir la autenticación de Firebase, haz lo siguiente:

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 admitir la autenticación de Auth0, haz lo siguiente:

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 admitir la autenticación mediante un token de ID de Google, sigue estos pasos:

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

personalizado

Para admitir la autenticación personalizada, sigue estos pasos:

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

Para la autenticación de Firebase, el campo audiences es obligatorio y debe ser el ID de tu proyecto de Firebase. En el resto de los métodos de autenticación, es opcional. Si no se especifica, ESP acepta todos los JWTs con el nombre del servicio backend en el formato https://SERVICE_NAME en la reclamación aud. Para permitir que otros IDs de cliente accedan al servicio backend, puede especificar los IDs de cliente permitidos en el campo audiences mediante valores separados por comas. A continuación, el ESP acepta los JWTs con los IDs de cliente incluidos en la lista blanca en la reclamación aud.

Llamar a un método autenticado desde gRPC

Si un método requiere autenticación, los clientes de gRPC deben enviar el token de autenticación como metadatos con la llamada al método, donde la clave es authorization y el valor es Bearer <JWT_TOKEN>. A continuación se muestra un ejemplo de cómo hacerlo al llamar al 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

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/grpc-js');
  const protoLoader = require('@grpc/proto-loader');
  const path = require('path');

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

  const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
    keepCase: true,
    longs: String,
    enums: String,
    defaults: true,
    oneofs: true,
  });

  const protoObj = grpc.loadPackageDefinition(packageDefinition).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 enviarlo depende del método de autenticación.

Recibir resultados de autenticación en tu API

Normalmente, los ESP reenvían todos los encabezados que reciben. Sin embargo, anula el encabezado Authorization original cuando la dirección del backend se especifica mediante x-google-backend en la especificación de OpenAPI o BackendRule en la configuración del servicio gRPC.

El ESP enviará el resultado de la autenticación en el X-Endpoint-API-UserInfo a la API backend. Te recomendamos que uses este encabezado en lugar del encabezado Authorization original. Este encabezado es una cadena que base64url codifica un objeto JSON. El formato de objeto JSON es diferente en ESPv2 y ESP. En ESPv2, el objeto JSON es exactamente la carga útil del JWT original. En el caso de los ESPs, el objeto JSON usa nombres de campo diferentes y coloca la carga útil del JWT original en el campo claims. Consulta Gestionar JWTs en el servicio backend para obtener más información sobre el formato.

Siguientes pasos