Autenticar usuários

Com a autenticação, o Extensible Service Proxy (ESP) pode identificar os usuários que estão chamando seus métodos de serviço e, com base nisso, decidir se eles poderão usá-los ou não (autorização). Nesta página, descrevemos como a autenticação funciona com os serviços do Cloud Endpoints para gRPC, o que inclui como configurar o ESP em um serviço gRPC para ser compatível com as solicitações autenticadas e como chamar os métodos autenticados de um cliente gRPC.

O ESP é compatível com vários métodos de autenticação, incluindo o Firebase, o Auth0 e os tokens de código do Google, que podem ser definidos como parte da configuração da API gRPC. Em cada caso, a solicitação do cliente precisa conter um token da Web JSON (JWT, na sigla em inglês) para identificação. No ESP, o token é validado em nome da API, de modo que você não precisa adicionar nenhum código de autenticação especial.

Embora os requisitos de autenticação e chave de API permitam restringir quem pode chamar os métodos de serviço, eles não têm o mesmo nível de segurança e disponibilizam informações diferentes ao serviço chamado. Saiba mais sobre as diferenças entre as chaves de API e a autenticação e quando usar cada esquema em Por que e quando usar chaves de API.

Para ver um exemplo prático e completo do uso da autenticação, veja Como fazer autenticação usando uma conta de serviço, em que a autenticação é adicionada ao serviço Bookstore, nos nossos tutoriais.

Configurar a autenticação do ESP

Configure a autenticação de um serviço do Endpoints para gRPC no respectivo arquivo YAML da configuração do serviço gRPC. Basta usar a seção authentication. Especifique o método de autenticação e os detalhes da origem da autenticação como providers, em que:

  • O valor id é usado para identificar o provedor de autenticação quando usado em rules: normalmente, ele usa o nome do método de autenticação, mas não ele não é necessário;

  • o valor issuer é o emissor dos tokens obrigatórios e, portanto, especifica o método de autenticação;

  • O valor jwks_uri é o URI da chave pública do provedor, usado para validar tokens. Alguns métodos de autenticação não exigem que você especifique esse valor, como os tokens de código do Google, local do qual o ESP retira as informações automaticamente.

  • O jwt_locations é usado para definir os locais para extrair o JWT.

É necessário definir vários provedores de segurança no mesmo arquivo, mas cada um precisa ter um issuer diferente. Consulte AuthProvider para mais informações.

Especifique quais métodos da API precisam usar esses requisitos de autenticação com rules, como descrito em AuthenticationRule.

Os exemplos a seguir mostram como configurar o ESP em um serviço do gRPC em alguns métodos de autenticação compatíveis.

firebase

Para dar suporte à autenticação 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 dar suporte à autenticação 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 código do Google

Para dar suporte à autenticação usando um token de código do 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

personalizada

Para aceitar a autenticação 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

Para o Firebase Authentication, o campo audiences é obrigatório e precisa ser seu ID do projeto do Firebase. Para todos os outros métodos de autenticação, ele é opcional. Se não for especificado, o ESP aceitará todos os JWTs com o nome do serviço de back-end na forma de https://SERVICE_NAME na declaração aud. Para colocar outros IDs de cliente na lista de permissões de acesso ao serviço de back-end, especifique os IDs de cliente permitidos no campo audiences com valores separados por vírgulas. Em seguida, o ESP aceita, por meio da declaração aud, os JWTs com os IDs de cliente na lista de permissões.

Chamar um método autenticado do gRPC

Se um método exigir autenticação, o token de autenticação precisará ser transmitido pelos clientes gRPC como metadados com a chamada do respectivo método, em que a chave é authorization e o valor é Bearer <JWT_TOKEN>. Veja um exemplo de como fazer isso na chamada do exemplo do Bookstore em 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('{}:{}'.format(host, port), creds, channel_opts)
    else:
        channel = grpc.insecure_channel('{}:{}'.format(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('ListShelves: {}'.format(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);
    }
  });
};

A maneira como um JWT válido para envio é recebido pelo cliente depende do método de autenticação.

Como receber os resultados da autenticação na API

O ESP geralmente encaminha todos os cabeçalhos recebidos. No entanto, ele substitui o cabeçalho Authorization original quando o endereço de back-end é especificado por x-google-backend na especificação OpenAPI ou BackendRule na configuração do serviço de gRPC.

O ESP enviará o resultado da autenticação no X-Endpoint-API-UserInfo para a API de back-end. Recomendamos o uso deste cabeçalho em vez do cabeçalho Authorization original. Esse cabeçalho é uma string que base64url codifica um objeto JSON. O formato do objeto JSON é diferente no ESPv2 e no ESP. Para ESPv2, o objeto JSON é exatamente o payload original do JWT. Para ESP, o objeto JSON usa nomes de campo diferentes e coloca o payload do JWT original em claims. Consulte Gerenciar JWTs no serviço de back-end para mais informações sobre o formato.

A seguir