GCP Cloud APIs supports a variety of system-headers
described here
This section will briefly cover the following headers, when you would use them and how to add them to your API calls.
This is the standard header used to authenticate in both REST and gRPC. For REST, it can be set either as a query parameter (&access_token
) or as an HTTP header in the form Authorization: Bearer [access_token]
. For gRPC, this header is sent in within a gRPC metadata key. Thee tokens are automatically included and attached in when using the GCP Cloud Libraries after local authentication credentials are acquired though Application Default Credentials
These authentication tokens come in several flavors but principally these tokens are Oauth2 tokens issued by Google through one of the flows described in Using OAuth 2.0 to Access Google APIs.
GCP also supports several other token types and mechanisms to exchange third party identities for GCP credentials but ultimately, these tokens are emitted to GCP using this header value.
access_token
: These are oauth2 access tokens that generally grant access to the GCP resource associated with that user subject to the scopes defined within that token.
id_token
: These are OpenID Connect tokens issued by Google that is used by callers against user-deployable resource. In this context, a user resource is an application you deploy on a service like Cloud Run, Cloud Functions, Cloud Endpoints. These tokens are also emitted by certain GCP services to authenticate itself to remote services. For more information, see Authenticating using Google OpenID Connect Tokens
jwt_access_token
: These tokens are a Google-specific oauth to optimization casually described in an Addendum: Service account authorization without OAuth. Basically, this flow uses a service account key to generate a specific JWT which indicates the destination service and is signed by a service account entitled to access that service. The JWT is sent directly to the service and does not involve an intermediate exchange for an access_token
. For more information, see Using JWT AccessTokens.
You can print out the details of these tokens using curl and the oauth2 tokeninfo
endpoint shown below. id_tokens
are just JWTs which you can decode anywhere (eg jwt.io)
$ curl -s https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=`gcloud auth print-access-token` | jq '.'
{
"azp": "32555940559.apps.googleusercontent.com",
"aud": "32555940559.apps.googleusercontent.com",
"sub": "1081579130932748411228",
"scope": "openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/appengine.admin https://www.googleapis.com/auth/compute https://www.googleapis.com/auth/accounts.reauth",
"exp": "1640026115",
"expires_in": "3598",
"email": "user@domain.com",
"email_verified": "true"
}
$ curl -s https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=`gcloud auth application-default print-access-token` | jq '.'
{
"azp": "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com",
"aud": "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com",
"sub": "1081579130932748411228",
"scope": "openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/accounts.reauth",
"exp": "1640026184",
"expires_in": "3598",
"email": "user@domain.com",
"email_verified": "true"
}
$ curl -s https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=`gcloud auth print-identity-token` | jq '.'
{
"iss": "https://accounts.google.com",
"azp": "32555940559.apps.googleusercontent.com",
"aud": "32555940559.apps.googleusercontent.com",
"sub": "10449703227021975232",
"hd": "domain.com",
"email": "user1@domain.com",
"email_verified": "true",
"at_hash": "tBcn_NJEadsztkeLPtna6A",
"iat": "1640024536",
"exp": "1640028136",
"alg": "RS256",
"kid": "d98f49bc6ca4581eae8dfadd494fce10ea23aab0",
"typ": "JWT"
}
You might be wondering what the audiences 32555940559.apps.googleusercontent.com
and 764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com
are. These are the client_id
associated with the gcloud and Application Default credentials, respectively. For more info on that, see gcloud alias for Application Default Credentials
You can use these headers directly in curl based commands
The following acquires an access_token
and calls a GCP resource
export TOKEN=`gcloud auth print-access-token`
curl -s -H "Authorization: Bearer $TOKEN" https://storage.googleapis.com/storage/v1/b/$BUCKET/o/foo.txt
The following issues an id_token
with a STATIC aud:
value of 32555940559.apps.googleusercontent.com
. Normally, cloud run requires the audience value to match the service name. However, a special allow list was granted for Cloud Run here that allows this through. The following will NOT work for resourced protected by IAP.
export ID_TOKEN=`gcloud auth print-identity-token`
curl -H "Authorization: Bearer $ID_TOKEN" https://cloud_run_url
The following uses service accounts own id_token and then specifies the audience the token should have. The value here matches what Cloud Run expects so the request is allowed through
export ID_TOKEN=`gcloud auth print-identity-token --impersonate-service-account=target-svc-account@developer.gserviceaccount.com --audiences=https://cloud_run_url`
curl -H "Authorization: Bearer $ID_TOKEN" https://cloud_run_url
The default access_token
is emitted by default through Application Default Credentials flow. Using id_token
and jwt_access_tokens
may require special constructors
For usage with jwt_access_tokens
. Note this is currently a rare API auth flow and must be done by the caller.
Use google.auth.jwt.Credentials and specify the audience=
value as shown
TODO: sample
Use google.JWTAccessTokenSourceFromJSON
aud := "https://pubsub.googleapis.com/google.pubsub.v1.Publisher"
tokenSource, err := google.JWTAccessTokenSourceFromJSON(keyBytes, aud)
client, err := pubsub.NewClient(ctx, *projectID, option.WithTokenSource(tokenSource))
Use com.google.auth.oauth2.JwtProvider
TODO: sample
FieldMasks are used to request Partial Responses from a GCP API.
For more information on using it, see examples in Using FieldMask
Google API keys are used by some services metering and billing. Almost no GCP service uses API key alone and elects to use the oauth2 bearer tokens described above.
However, some services like the Natural Language APIs allows for both types of tokens. If an API key is used, its a static token that must be handled and secured. When the API key is used, the project that is associated with the key incurs quota and usage costs.
For example, using API key from ProjectA, the consumer project needs the service enabled (thats still projectA)
$ curl --request POST "https://language.googleapis.com/v1/documents:analyzeEntities?key=$API_KEY" \
--header 'Accept: application/json' --header 'Content-Type: application/json' \
--data '{"document":{"content":"Hello World","type":"PLAIN_TEXT"}}'
{
"error": {
"code": 403,
"message": "Cloud Natural Language API has not been used in project 248066739555 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/language.googleapis.com/overview?project=248066739555 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.",
"status": "PERMISSION_DENIED",
"details": [
{
"@type": "type.googleapis.com/google.rpc.Help",
"links": [
{
"description": "Google developers console API activation",
"url": "https://console.developers.google.com/apis/api/language.googleapis.com/overview?project=248066739555"
}
]
},
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "SERVICE_DISABLED",
"domain": "googleapis.com",
"metadata": {
"service": "language.googleapis.com",
"consumer": "projects/248066739555"
}
}
]
}
}
After api enablement, calls succeed and the API usage is logged
$ curl --request POST "https://language.googleapis.com/v1/documents:analyzeEntities?key=$API_KEY" --header 'Accept: application/json' --header 'Content-Type: application/json' --data '{"document":{"content":"Hello World","type":"PLAIN_TEXT"}}'
{
"entities": [
{
"name": "Hello World",
"type": "WORK_OF_ART",
"metadata": {
"mid": "/m/03lm3",
"wikipedia_url": "https://en.wikipedia.org/wiki/\"Hello,_World!\"_program"
},
"salience": 1,
"mentions": [
{
"text": {
"content": "Hello World",
"beginOffset": -1
},
"type": "PROPER"
}
]
}
],
"language": "en"
}
For more information, see
Cloud Endpoints also uses it to rate limit quota (Configuring quotas)
TODO
TODO
const language = require('@google-cloud/language');
const {GoogleAuth, grpc} = require('google-gax');
const apiKey = 'AIza-API_KEY';
function getApiKeyCredentials() {
const sslCreds = grpc.credentials.createSsl();
const googleAuth = new GoogleAuth();
const authClient = googleAuth.fromAPIKey(apiKey);
const credentials = grpc.credentials.combineChannelCredentials(
sslCreds,
grpc.credentials.createFromGoogleCredential(authClient)
);
return credentials;
}
async function main() {
const sslCreds = getApiKeyCredentials();
const client = new language.LanguageServiceClient({sslCreds});
const text = 'Hello, world!';
const document = {
content: text,
type: 'PLAIN_TEXT',
};
const [result] = await client.analyzeSentiment({document: document});
const sentiment = result.documentSentiment;
console.log(`Text: ${text}`);
console.log(`Sentiment score: ${sentiment.score}`);
console.log(`Sentiment magnitude: ${sentiment.magnitude}`);
}
main().catch(console.error);
This parameter is normally used with the API key to identity and control rate limits per caller.
It is not used in Google Cloud APIs.
The x-goog-api-client
is intended for use by Google-written clients for metrics.
Users can submit UserAgent metadata in api calls. option.WithUserAgent
Also See Request Annotation with Cloud Audit Logging and Monitoring on GCP
Used to allow the caller to redirect quota and billing. Requires the caller to have the serviceusage.services.use
permission on the target project
See GCP Quota and Cost Distribution between Projects
Use client_options.quota_project_id()
quota_project_id (Optional[str]) – A project name that a client’s quota belongs to.
TODO
To set arbitrary Headers for REST (GCS) and GRPC services (pubsub), you’ll need special handling:
#!/usr/bin/python
# virtualenv env
# source env/bin/activate
# pip install google-cloud-pubsub google-cloud storage
import os
import time
import google.auth.credentials
import google.auth.jwt
import google.auth.transport.grpc
import grpc
import google.auth.transport.requests
from google.auth.transport.requests import AuthorizedSession
# from google.pubsub_v1.services.publisher import PublisherAsyncClient
# from google.pubsub_v1.services.publisher import PublisherClient
from google.cloud import pubsub_v1
from google.pubsub_v1.services.publisher import transports
# https://github.com/grpc/grpc/tree/master/examples/python/interceptors/headers
import header_manipulator_client_interceptor
import six
project_id = "projectID"
allow = "foo"
header = 'x-goog-custom-header'
credentials, _ = google.auth.default()
request = google.auth.transport.requests.Request()
## Pubsub
header_adder_interceptor = header_manipulator_client_interceptor.header_adder_interceptor(header,deny)
channel = google.auth.transport.grpc.secure_authorized_channel(
credentials=credentials, request=request, target=pubsub_v1.PublisherClient.SERVICE_ADDRESS,
ssl_credentials=grpc.ssl_channel_credentials())
intercept_channel = grpc.intercept_channel(channel, header_adder_interceptor)
transport = transports.PublisherGrpcTransport(channel=intercept_channel)
publisher =pubsub_v1.PublisherClient(transport=transport)
topic_name = 'projects/{project_id}/topics/{topic}'.format(
project_id=project_id,
topic='topic1',
)
topic = publisher.topic_path(project_id, 'topic1')
data = b'The rain in Wales falls mainly on the snails.'
for n in range(1, 2):
future = publisher.publish(topic, data)
print(future.result())
print("Published messages.")
### GCS
authed_session = AuthorizedSession(credentials)
authed_session.headers[header] = allow
from google.cloud import storage
client = storage.Client(_http=authed_session)
buckets = client.list_buckets()
for bkt in buckets:
print(bkt)
package main
import (
"fmt"
"io"
"net/http"
"os"
"google.golang.org/api/option"
pubsub "cloud.google.com/go/pubsub"
"cloud.google.com/go/storage"
"golang.org/x/net/context"
"golang.org/x/oauth2/google"
"google.golang.org/api/iterator"
)
const (
projectID = "projectid"
bucketName = "golang-test-bucket"
fileName = "foo.txt"
allow = "foo"
)
type headerTransportLayer struct {
http.Header
baseTransit http.RoundTripper
}
func newTransportWithHeaders(baseTransit http.RoundTripper, key, value string) headerTransportLayer {
if baseTransit == nil {
baseTransit = http.DefaultTransport
}
headers := make(http.Header)
headers.Set(key, value)
return headerTransportLayer{Header: headers, baseTransit: baseTransit}
}
func (h headerTransportLayer) RoundTrip(req *http.Request) (*http.Response, error) {
for key, value := range h.Header {
// only set headers that are not previously defined
if _, ok := req.Header[key]; !ok {
req.Header[key] = value
}
}
return h.baseTransit.RoundTrip(req)
}
func main() {
ctx := context.Background()
// for pubsub grpc
client, err := pubsub.NewClient(ctx, projectID)
if err != nil {
fmt.Printf("Could not create pubsub Client: %v", err)
return
}
defer client.Close()
ctx = metadata.AppendToOutgoingContext(ctx, "x-goog-custom-header", deny)
t := client.Topic("testing")
ok, err := t.Exists(ctx)
if err != nil {
fmt.Printf("Could not check pubsub tpic: %v\b", err)
return
}
fmt.Printf("Topic Exists %v\n", ok)
r := t.Publish(ctx, &pubsub.Message{
Data: []byte("foo"),
})
mid, err := r.Get(ctx)
if err != nil {
fmt.Printf("Could not create pubsub Client: %v", err)
return
}
fmt.Printf("Published %s\n", mid)
// for storage http
hc, err := google.DefaultClient(ctx)
if err != nil {
fmt.Printf("%v", err)
return
}
hc.Transport = newTransportWithHeaders(hc.Transport, "x-goog-custom-header", allow)
storageClient, err := storage.NewClient(ctx, option.WithHTTPClient(hc))
if err != nil {
fmt.Printf("Unable to acquire storage Client: %v\n", err)
return
}
package com.test;
import java.util.Map;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.api.gax.rpc.FixedHeaderProvider;
import com.google.api.gax.rpc.HeaderProvider;
import com.google.api.gax.rpc.TransportChannelProvider;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.pubsub.v1.TopicAdminClient;
import com.google.cloud.pubsub.v1.TopicAdminClient.ListTopicsPagedResponse;
import com.google.cloud.pubsub.v1.TopicAdminSettings;
import com.google.cloud.storage.Blob;
import com.google.cloud.storage.Bucket;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;
import com.google.common.collect.ImmutableMap;
import com.google.pubsub.v1.ListTopicsRequest;
import com.google.pubsub.v1.ProjectName;
import com.google.pubsub.v1.Topic;
public class TestApp {
public static void main(String[] args) {
TestApp tc = new TestApp();
}
public TestApp() {
try {
String project_id = "projectid";
String bucketName = "golang-test-bucket";
String objectName = "foo.txt";
String allow = "foo";
String header = "x-goog-custom-header";
GoogleCredentials sourceCredentials = GoogleCredentials.getApplicationDefault();
Map<String, String> mergedHeaders = ImmutableMap.<String, String>builder()
.put(header, deny)
.build();
HeaderProvider headerProvider = FixedHeaderProvider.create(mergedHeaders);
Storage storage_service = StorageOptions.newBuilder().setCredentials(sourceCredentials)
.setHeaderProvider(headerProvider).build().getService();
Blob blob = storage_service.get(bucketName, objectName, Storage.BlobGetOption.fields(Storage.BlobField.values()));
System.out.println("Blob bucket " + blob.getBucket());
for (Bucket b : storage_service.list().iterateAll()) {
System.out.println(b);
}
FixedCredentialsProvider credentialsProvider = FixedCredentialsProvider.create(sourceCredentials);
TransportChannelProvider channelProvider = TopicAdminSettings.defaultTransportChannelProvider();
TopicAdminClient topicClient = TopicAdminClient.create(TopicAdminSettings.newBuilder()
.setTransportChannelProvider(channelProvider).setHeaderProvider(headerProvider)
.setCredentialsProvider(credentialsProvider).build());
ListTopicsRequest listTopicsRequest = ListTopicsRequest.newBuilder()
.setProject(ProjectName.format(project_id)).build();
ListTopicsPagedResponse response = topicClient.listTopics(listTopicsRequest);
Iterable<Topic> topics = response.iterateAll();
for (Topic topic : topics)
System.out.println(topic);
} catch (Exception ex) {
System.out.println("Error: " + ex);
}
}
}
const PubSub = require('@google-cloud/pubsub');
const Storage = require('@google-cloud/storage');
var GoogleAuth = require('google-auth-library');
var CallOptions = require('google-gax');
var project_id = "projectid";
var bucketName = "grpc-test-bucket";
var objectName = "foo.txt";
var allow = "foo";
var header = "x-goog-custom-header";
async function main() {
const pubSubClient = new PubSub(
{
projectId: project_id,
}
);
async function listAllTopics() {
options = {
autoPaginate: false,
gaxOpts: {
otherArgs: {
headers: {
"x-goog-custom-header": deny
}
}
}
}
const [topics] = await pubSubClient.getTopics(options);
console.log('Topics:');
topics.forEach(topic => console.log(topic.name));
}
listAllTopics().catch(console.error);
const storage = new Storage();
storage.interceptors.push({
request: reqOpts => {
reqOpts.headers = {
"x-goog-custom-header" : deny
};
return reqOpts
}
})
storage.getBuckets(function (err, buckets) {
if (err) {
console.log(err);
}
if (!err) {
buckets.forEach(function (value) {
console.log(value.id);
});
}
});
const contents = await storage.bucket(bucketName).file(objectName).download();
console.log(contents);
}
main().catch(console.error);
This site supports webmentions. Send me a mention via this form.