Authenticating Users

Authentication lets the Extensible Service Proxy (ESP) identify the users calling your service's methods and then, based on this, decide whether to let them use that method (authorization). This page describes how authentication works with gRPC Endpoints services, including how to configure the ESP in a gRPC service to support authenticated requests, and how to call authenticated methods from a gRPC client.

ESP supports multiple authentication methods, including Firebase, Auth0, and Google ID tokens, all of which can be set up as part of your gRPC API Configuration. In each case the client needs to provide an identifying JSON web token (JWT) in their requests. The ESP validates the token on behalf of your API, so you don't need to add any special authentication code yourself.

Note that while both authentication and API key requirements let you restrict who can call your service's methods, they do not provide the same level of security, and provide different information to the called service. You can find out more about the differences between API keys and authentication, and when it's appropriate to use each scheme, in When and Why to Use API Keys.

For a complete working example using authentication, see Using Service-to-Service Authentication, which adds authentication to the Bookstore service from our Tutorials.

Configuring authentication for ESP

You configure authentication for a gRPC Endpoints service in its gRPC API Configuration YAML file, using the authentication section. You specify the authentication method and details of the authentication source as providers, where:

  • The id value is used to identify the auth provider when used in rules: this typically uses the name of the authentication method, but it doesn't have to.
  • The issuer value is the issuer of the required tokens, and hence specifies the authentication method.
  • The jwks_uri value is the URI of the provider's public key, used to validate tokens. Some authentication methods don't require you to specify this, such as Google ID tokens where the ESP obtains the information automatically.

You can define multiple security providers in the same file, but each one must have a different issuer.

You specify the API methods that you want to use these authentication requirements using rules, as described in Configuring a gRPC API Service.

The following examples show how to set up ESP in a gRPC service for some supported authentication methods:

firebase

authentication:
  providers:
  - id: firebase
    jwks_uri: https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken@system.gserviceaccount.com
    # Replace YOUR-PROJECT-ID with your project ID
    issuer: https://securetoken.google.com/YOUR-PROJECT-ID
  rules:
  - selector: "*"
    requirements:
      - provider_id: firebase

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"
  rules:
  - selector: "*"
    requirements:
      - provider_id: auth0_jwk

service-to-service

authentication:
  providers:
  - id: google_service_account
    # Replace SERVICE-ACCOUNT-ID with your service account's email address.
    issuer: SERVICE-ACCOUNT-ID
    jwks_uri: https://www.googleapis.com/robot/v1/metadata/x509/SERVICE-ACCOUNT-ID
  rules:
  # This auth rule will apply to all methods.
  - selector: "*"
    requirements:
      - provider_id: google_service_account

custom

authentication:
  providers:
  - id: custom_auth_id
    # The value below should be unique
    issuer: issuer of the token
    jwks_uri: url to the public key
 rules:
 - selector: "*"
   requirements:
     - provider_id: custom_auth_id

Calling an authenticated method from gRPC

If a method requires authentication, gRPC clients need to pass the authentication token as metadata with their method call, where the key is authorization and the value is Bearer <JWT_TOKEN>. Select a tab below to see an example of how to do this when calling the Bookstore example in Python, Node.js, or Java:

Python

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

    channel = grpc.insecure_channel('{}:{}'.format(host, port))

    stub = bookstore_pb2.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

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

How the client gets a valid JWT to send depends on the authentication method: some are more complicated than others!

Receiving auth results in your API

Authentication result is passed to gRPC service as gRPC metadata x-endpoint-api-userinfo. You can find out how this works for OpenAPI APIs in Authenticating Users (OpenAPI).

Send feedback about...

Cloud Endpoints with gRPC