Authenticating service-to-service

When building applications that connect multiple services, it's a good idea to ensure that each service is only able to make requests to certain services. For instance, if you have a login service, it should be able to access the user profiles service, but it probably shouldn't be able to access the search service.

First, you'll need to configure the receiving service to accept requests from the calling service:

  1. Grant the Cloud Run Invoker (roles/run.invoker) role to the calling service identity on the receiving service. By default, this identity is PROJECT_NUMBER-compute@developer.gserviceaccount.com.

Console UI

  1. Go to the Google Cloud Platform Console:

    Go to Google Cloud Platform Console

  2. Select the receiving service.

  3. Click Show Info Panel in the top right corner to show the Permissions tab.

  4. In the Add members field, enter the identity of the calling service.

  5. Select the Cloud Run Invoker role from the Select a role drop-down menu.

  6. Click Add.

GCloud

Use the gcloud beta run services add-iam-policy-binding command:

gcloud beta run services add-iam-policy-binding RECEIVING_SERVICE \
  --member='serviceAccount:CALLING_SERVICE_IDENTITY' \
  --role='roles/run.invoker'

where RECEIVING_SERVICE is the name of the receiving service, and CALLING_SERVICE_IDENTITY is the email address of the service account.

In the calling service, you'll need to:

  1. Create a Google-signed OAuth ID token with the audience (aud) set to the URL of the receiving service. This value must contain the schema prefix (http:// or https://) and custom domains are currently not supported for the aud value.

  2. Include the ID token in an Authorization: Bearer ID_TOKEN header in the request to the service.

Nodejs
// Make sure to `npm install --save request-promise` or add the dependency to your package.json
const request = require('request-promise');

const receivingServiceURL = ...

// Set up metadata server request
// See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
const metadataServerTokenURL = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
const tokenRequestOptions = {
    uri: metadataServerTokenURL + receivingServiceURL,
    headers: {
        'Metadata-Flavor': 'Google'
    }
};

// Fetch the token, then provide the token in the request to the receiving service
request(tokenRequestOptions)
  .then((token) => {
    return request(receivingServiceURL).auth(null, null, true, token)
  })
  .then((response) => {
    res.status(200).send(response);
  })
  .catch((error) => {
    res.status(400).send(error);
  });
};
    
Python
# Requests is already installed, no need to add it to requirements.txt
import requests

receiving_service_url = ...

# Set up metadata server request
# See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
metadata_server_token_url = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience='

token_request_url = metadata_server_token_url + receiving_service_url
token_request_headers = {'Metadata-Flavor': 'Google'}

# Fetch the token
token_response = requests.get(token_request_url, headers=token_request_headers)
jwt = token_response.content.decode("utf-8")

# Provide the token in the request to the receiving service
receiving_service_headers = {'Authorization': f'bearer {jwt}'}
service_response = requests.get(receiving_service_url, headers=receiving_service_headers)

return service_response.content
    
Go
import (
	"fmt"
	"net/http"

	"cloud.google.com/go/compute/metadata"
)

// makeGetRequest makes a GET request to the specified Cloud Run endpoint in
// serviceURL (must be a complete URL) by authenticating with the ID token
// obtained from the Metadata API.
func makeGetRequest(serviceURL string) (*http.Response, error) {
	// query the id_token with ?audience as the serviceURL
	tokenURL := fmt.Sprintf("/instance/service-accounts/default/identity?audience=%s", serviceURL)
	idToken, err := metadata.Get(tokenURL)
	if err != nil {
		return nil, fmt.Errorf("metadata.Get: failed to query id_token: %+v", err)
	}
	req, err := http.NewRequest("GET", serviceURL, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", idToken))
	return http.DefaultClient.Do(req)
}

Calling from outside GCP

If you're invoking a service from a compute instance that doesn't have access to compute metadata (e.g. your own server), you'll have to manually generate the proper token:

  1. Self-sign a service account JWT with the target_audience claim set to the URL of the receiving service.

  2. Exchange the self-signed JWT for a Google-signed ID token, which should have the aud claim set to the above URL.

  3. Include the ID token in an Authorization: Bearer ID_TOKEN header in the request to the service.

The Cloud IAP docs have sample code to demonstrate this functionality.

Was this page helpful? Let us know how we did:

Send feedback about...

Cloud Run Documentation