Create a downscoped short-lived credential
This page explains how to use Credential Access Boundaries to create an OAuth 2.0 access token with downscoped Cloud Storage permissions.
The process for creating a token with downscoped permissions includes the following steps:
- Grant the appropriate IAM roles to a user or service account.
- Define a Credential Access Boundary that sets an upper bound on the permissions that are available to the user or service account.
- Create an OAuth 2.0 access token for the user or service account.
- Exchange the OAuth 2.0 access token for a new token that respects the Credential Access Boundary.
You can then use the new downscoped OAuth 2.0 access token to authenticate requests to Cloud Storage.
Before you begin
Before you use Credential Access Boundaries, make sure you meet the following requirements:
You need to downscope permissions only for Cloud Storage, not for other Google Cloud services.
If you need to downscope permissions for additional Google Cloud services, you can create multiple service accounts and grant a different set of roles to each service account.
You can use OAuth 2.0 access tokens for authentication. Other types of short-lived credentials don't support Credential Access Boundaries.
Also, you must enable the required APIs:
-
Enable the IAM and Security Token Service APIs.
Roles required to enable APIs
To enable APIs, you need the Service Usage Admin IAM role (
roles/serviceusage.serviceUsageAdmin), which contains theserviceusage.services.enablepermission. Learn how to grant roles.
Grant IAM roles
A Credential Access Boundary sets an upper bound on the available permissions for a resource. It can subtract permissions from a principal, but it can't add permissions that the principal does not already have.
As a result, you must also grant roles to the principal that provide the permissions they need, either on a Cloud Storage bucket or on a higher-level resource, such as the project.
For example, suppose you need to create a downscoped short-lived credential that allows a service account to create objects in a bucket:
- At a minimum, you must grant a role to the service account that includes the
storage.objects.createpermission, such as the Storage Object Creator role (roles/storage.objectCreator). The Credential Access Boundary must also include this permission. - You can also grant a role that includes more permissions, such as the Storage
Object Admin role (
roles/storage.objectAdmin). The service account can use only the permissions that appear in both the role grant and the Credential Access Boundary.
To learn about predefined roles for Cloud Storage, see Cloud Storage roles.
Define the Credential Access Boundary
A Credential Access Boundary is an object that contains a list of access boundary rules. The rules are made up of parameters that specify the upper bound of the permissions that are available to the user or service account. To define a Credential Access Boundary, create a JSON object that lists the access boundary rules and their parameters.
The following is an example of a Credential Access Boundary:
{
"accessBoundary":{
"accessBoundaryRules":[
{
"availablePermissions":[
"inRole:ROLE_ID"
],
"availableResource":"//storage.googleapis.com/projects/_/buckets/BUCKET_NAME"
"availabilityCondition":{
"expression":"CONDITION"
}
]
}
}
Replace the following:
ROLE_ID: The ID of a predefined or custom role that defines the upper bound on the available permissions for the resource. For example,roles/storage.objectViewer. To specify multiple roles, add a new line with aninRole:prefix followed by the role ID. Only the permissions in the specified roles will be available.BUCKET_NAME: The name of the Cloud Storage bucket that the rule applies to.CONDITION: Optional. A condition expression that specifies the Cloud Storage objects where permissions are available. For example, the following condition makes permissions available for objects whose name starts withcustomer-a:resource.name.startsWith('projects/_/buckets/example-bucket/objects/customer-a')
To learn more about how to create and customize credential access boundaries, see Components of a Credential Access Boundary.
For examples of potential use cases for Credential Access Boundaries, see Examples of Credential Access Boundaries.
Create an OAuth 2.0 access token
Before you create a downscoped short-lived credential, you must create a normal
OAuth 2.0 access token. You can then exchange the normal credential for a
downscoped credential. When you create the access token, use the OAuth 2.0 scope
https://www.googleapis.com/auth/cloud-platform.
To create an access token for a service account, you can complete the server-to-server OAuth 2.0 flow, or you can use the Service Account Credentials API to generate an OAuth 2.0 access token.
To create an access token for a user, see Obtaining OAuth 2.0 access tokens. You can also use the OAuth 2.0 Playground to create an access token for your own Google Account.
Exchange the OAuth 2.0 access token
After you create an OAuth 2.0 access token, you can exchange the access token for a downscoped token that respects the Credential Access Boundary. This process typically involves a token broker and a token consumer:
The token broker is responsible for defining the Credential Access Boundary and exchanging an access token for a downscoped token.
The token broker can use a supported authentication library to exchange access tokens automatically, or it can call the Security Token Service to exchange tokens manually.
The token consumer requests a downscoped access token from the token broker, then uses the downscoped access token to perform another action.
The token consumer can use a supported authentication library to automatically refresh access tokens before they expire. Alternatively, it can refresh tokens manually, or it can allow tokens to expire without refreshing them.
There are two ways to exchange the access token for a downscoped token:
Client-side token exchange (recommended): Clients obtain cryptographic materials from the Security Token Service API server. The cryptographic materials allow clients to generate downscoped tokens with varying Credential Access Boundary rules independently on the client side for a set duration (for example, 1 hour). This approach reduces latency and improves efficiency, especially for clients requiring frequent Credential Access Boundary rule updates. It's also more efficient for applications that need to generate many unique downscoped tokens. This is the recommended approach because it provides better performance, scalability, and future feature compatibility.
Server-side token exchange: Clients request a new downscoped token from the Security Token Service API server whenever a Credential Access Boundary rule changes. This approach is straightforward but requires a round trip to the Security Token Service API server for each downscoped token request. This approach is recommended only for customers who require a client library that doesn't support client-side token exchange, due to the round trip to the Security Token Service API for each downscoped token request.
Client-side token exchange
If you create the token broker and token consumer with the following language, you can use Google's authentication library to exchange and refresh tokens automatically through the client-side approach.
Java
For Java, you can exchange and refresh tokens automatically with version 1.32.1
or later of the
com.google.auth:google-auth-library-cab-token-generator artifact.
To check your artifact version, run the following Maven command in your application directory:
mvndependency:list-DincludeArtifactIds=google-auth-library-cab-token-generator
The following example shows how a token broker can generate downscoped tokens:
importcom.google.auth.credentialaccessboundary.ClientSideCredentialAccessBoundaryFactory ;
importcom.google.auth.oauth2.AccessToken ;
importcom.google.auth.oauth2.CredentialAccessBoundary ;
importcom.google.auth.oauth2.GoogleCredentials ;
importdev.cel.common.CelValidationException;
importjava.io.IOException;
importjava.security.GeneralSecurityException;
publicstaticAccessToken getTokenFromBroker(StringbucketName,StringobjectPrefix)
throwsIOException{
// Retrieve the source credentials from ADC.
GoogleCredentials sourceCredentials=
GoogleCredentials .getApplicationDefault ()
.createScoped("https://www.googleapis.com/auth/cloud-platform");
// Initialize the Credential Access Boundary rules.
StringavailableResource="//storage.googleapis.com/projects/_/buckets/"+bucketName;
// Downscoped credentials will have readonly access to the resource.
StringavailablePermission="inRole:roles/storage.objectViewer";
// Only objects starting with the specified prefix string in the object name will be allowed
// read access.
Stringexpression=
"resource.name.startsWith('projects/_/buckets/"
+bucketName
+"/objects/"
+objectPrefix
+"')";
// Build the AvailabilityCondition.
CredentialAccessBoundary .AccessBoundaryRule .AvailabilityCondition availabilityCondition=
CredentialAccessBoundary .AccessBoundaryRule.AvailabilityCondition.newBuilder()
.setExpression (expression)
.build();
// Define the single access boundary rule using the above properties.
CredentialAccessBoundary .AccessBoundaryRule rule=
CredentialAccessBoundary .AccessBoundaryRule.newBuilder()
.setAvailableResource(availableResource)
.addAvailablePermission (availablePermission)
.setAvailabilityCondition (availabilityCondition)
.build();
// Define the Credential Access Boundary with all the relevant rules.
CredentialAccessBoundary credentialAccessBoundary=
CredentialAccessBoundary .newBuilder().addRule (rule).build();
// Create an instance of ClientSideCredentialAccessBoundaryFactory.
ClientSideCredentialAccessBoundaryFactory factory=
ClientSideCredentialAccessBoundaryFactory .newBuilder()
.setSourceCredential(sourceCredentials)
.build();
// Generate the token and pass it to the Token Consumer.
try{
returnfactory.generateToken (credentialAccessBoundary);
}catch(GeneralSecurityException|CelValidationExceptione){
thrownewIOException("Error generating downscoped token",e);
}
}The following example shows how a token consumer can use a refresh handler to automatically obtain and refresh downscoped tokens:
importcom.google.auth.oauth2.AccessToken ;
importcom.google.auth.oauth2.OAuth2CredentialsWithRefresh ;
importcom.google.cloud.storage.Blob ;
importcom.google.cloud.storage.Storage ;
importcom.google.cloud.storage.StorageOptions ;
importjava.io.IOException;
publicstaticStringretrieveBlobWithDownscopedToken(
finalStringbucketName,finalStringobjectName)throwsIOException{
// You can pass an `OAuth2RefreshHandler` to `OAuth2CredentialsWithRefresh` which will allow the
// library to seamlessly handle downscoped token refreshes on expiration.
OAuth2CredentialsWithRefresh .OAuth2RefreshHandler handler=
newOAuth2CredentialsWithRefresh .OAuth2RefreshHandler (){
@Override
publicAccessToken refreshAccessToken()throwsIOException{
// The common pattern of usage is to have a token broker pass the downscoped short-lived
// access tokens to a token consumer via some secure authenticated channel.
// For illustration purposes, we are generating the downscoped token locally.
// We want to test the ability to limit access to objects with a certain prefix string
// in the resource bucket. objectName.substring(0, 3) is the prefix here. This field is
// not required if access to all bucket resources are allowed. If access to limited
// resources in the bucket is needed, this mechanism can be used.
returnDownscopedAccessTokenGenerator
.getTokenFromBroker(bucketName,objectName);
}
};
AccessToken downscopedToken=handler.refreshAccessToken();
OAuth2CredentialsWithRefresh credentials=
OAuth2CredentialsWithRefresh .newBuilder()
.setAccessToken(downscopedToken)
.setRefreshHandler (handler)
.build();
StorageOptions options=StorageOptions .newBuilder().setCredentials(credentials).build();
Storage storage=options.getService ();
Blob blob=storage.get (bucketName,objectName);
if(blob==null){
returnnull;
}
returnnewString(blob.getContent ());
}Server-side token exchange
This section describes the following methods that you can use to exchange tokens through the service side approach:
Exchange and refresh the access token automatically using server-side approach
If you create the token broker and token consumer with one of the following languages, you can use Google's authentication library to exchange and refresh tokens automatically using the server-side token generation approach:
Go
For Go, you can exchange and refresh tokens automatically with version
v0.0.0-20210819190943-2bc19b11175f or later of the
golang.org/x/oauth2 package.
To check your package version, run the following command in your application directory:
golist-mgolang.org/x/oauth2
The following example shows how a token broker can generate downscoped tokens:
import(
"context"
"fmt"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/google/downscope"
)
// createDownscopedToken would be run on the token broker in order to generate
// a downscoped access token that only grants access to objects whose name begins with prefix.
// The token broker would then pass the newly created token to the requesting token consumer for use.
funccreateDownscopedToken(bucketNamestring,prefixstring)error{
// bucketName := "foo"
// prefix := "profile-picture-"
ctx:=context.Background()
// A condition can optionally be provided to further restrict access permissions.
condition:=downscope.AvailabilityCondition{
Expression:"resource.name.startsWith('projects/_/buckets/"+bucketName+"/objects/"+prefix+"')",
Title:prefix+" Only",
Description:"Restricts a token to only be able to access objects that start with `"+prefix+"`",
}
// Initializes an accessBoundary with one Rule which restricts the downscoped
// token to only be able to access the bucket "bucketName" and only grants it the
// permission "storage.objectViewer".
accessBoundary:=[]downscope.AccessBoundaryRule{
{
AvailableResource:"//storage.googleapis.com/projects/_/buckets/"+bucketName,
AvailablePermissions:[]string{"inRole:roles/storage.objectViewer"},
Condition:&condition,// Optional
},
}
// This Source can be initialized in multiple ways; the following example uses
// Application Default Credentials.
varrootSourceoauth2.TokenSource
// You must provide the "https://www.googleapis.com/auth/cloud-platform" scope.
rootSource,err:=google.DefaultTokenSource(ctx,"https://www.googleapis.com/auth/cloud-platform")
iferr!=nil{
returnfmt.Errorf("failed to generate rootSource: %w",err)
}
// downscope.NewTokenSource constructs the token source with the configuration provided.
dts,err:=downscope.NewTokenSource(ctx,downscope.DownscopingConfig{RootSource:rootSource,Rules:accessBoundary})
iferr!=nil{
returnfmt.Errorf("failed to generate downscoped token source: %w",err)
}
// Token() uses the previously declared TokenSource to generate a downscoped token.
tok,err:=dts.Token()
iferr!=nil{
returnfmt.Errorf("failed to generate token: %w",err)
}
// Pass this token back to the token consumer.
_=tok
returnnil
}
The following example shows how a token consumer can use a refresh handler to automatically obtain and refresh downscoped tokens:
import(
"context"
"fmt"
"io"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/google/downscope"
"cloud.google.com/go/storage"
"golang.org/x/oauth2"
"google.golang.org/api/option"
)
// A token consumer should define their own tokenSource. In the Token() method,
// it should send a query to a token broker requesting a downscoped token.
// The token broker holds the root credential that is used to generate the
// downscoped token.
typelocalTokenSourcestruct{
ctxcontext.Context
bucketNamestring
brokerURLstring
}
func(ltslocalTokenSource)Token()(*oauth2.Token,error){
varremoteToken*oauth2.Token
// Usually you would now retrieve remoteToken, an oauth2.Token, from token broker.
// This snippet performs the same functionality locally.
accessBoundary:=[]downscope.AccessBoundaryRule{
{
AvailableResource:"//storage.googleapis.com/projects/_/buckets/"+lts.bucketName,
AvailablePermissions:[]string{"inRole:roles/storage.objectViewer"},
},
}
rootSource,err:=google.DefaultTokenSource(lts.ctx,"https://www.googleapis.com/auth/cloud-platform")
iferr!=nil{
returnnil,fmt.Errorf("failed to generate rootSource: %w",err)
}
dts,err:=downscope.NewTokenSource(lts.ctx,downscope.DownscopingConfig{RootSource:rootSource,Rules:accessBoundary})
iferr!=nil{
returnnil,fmt.Errorf("failed to generate downscoped token source: %w",err)
}
// Token() uses the previously declared TokenSource to generate a downscoped token.
remoteToken,err=dts.Token()
iferr!=nil{
returnnil,fmt.Errorf("failed to generate token: %w",err)
}
returnremoteToken,nil
}
// getObjectContents will read the contents of an object in Google Storage
// named objectName, contained in the bucket "bucketName".
funcgetObjectContents(outputio.Writer ,bucketNamestring,objectNamestring)error{
// bucketName := "foo"
// prefix := "profile-picture-"
ctx:=context.Background()
thisTokenSource:=localTokenSource{
ctx:ctx,
bucketName:bucketName,
brokerURL:"yourURL.com/internal/broker",
}
// Wrap the TokenSource in an oauth2.ReuseTokenSource to enable automatic refreshing.
refreshableTS:=oauth2.ReuseTokenSource(nil,thisTokenSource)
// You can now use the token source to access Google Cloud Storage resources as follows.
storageClient,err:=storage.NewClient(ctx,option.WithTokenSource(refreshableTS))
iferr!=nil{
returnfmt.Errorf("failed to create the storage client: %w",err)
}
deferstorageClient.Close()
bkt:=storageClient.Bucket (bucketName)
obj:=bkt.Object (objectName)
rc,err:=obj.NewReader (ctx)
iferr!=nil{
returnfmt.Errorf("failed to retrieve the object: %w",err)
}
deferrc.Close()
data,err:=io.ReadAll(rc)
iferr!=nil{
returnfmt.Errorf("could not read the object's contents: %w",err)
}
// Data now contains the contents of the requested object.
output.Write (data)
returnnil
}
Java
For Java, you can exchange and refresh tokens automatically with version 1.1.0
or later of the
com.google.auth:google-auth-library-oauth2-http artifact.
To check your artifact version, run the following Maven command in your application directory:
mvndependency:list-DincludeArtifactIds=google-auth-library-oauth2-http
The following example shows how a token broker can generate downscoped tokens:
publicstaticAccessTokengetTokenFromBroker(StringbucketName,StringobjectPrefix)
throwsIOException{
// Retrieve the source credentials from ADC.
GoogleCredentialssourceCredentials=
GoogleCredentials.getApplicationDefault()
.createScoped("https://www.googleapis.com/auth/cloud-platform");
// Initialize the Credential Access Boundary rules.
StringavailableResource="//storage.googleapis.com/projects/_/buckets/"+bucketName;
// Downscoped credentials will have readonly access to the resource.
StringavailablePermission="inRole:roles/storage.objectViewer";
// Only objects starting with the specified prefix string in the object name will be allowed
// read access.
Stringexpression=
"resource.name.startsWith('projects/_/buckets/"
+bucketName
+"/objects/"
+objectPrefix
+"')";
// Build the AvailabilityCondition.
CredentialAccessBoundary.AccessBoundaryRule.AvailabilityConditionavailabilityCondition=
CredentialAccessBoundary.AccessBoundaryRule.AvailabilityCondition.newBuilder()
.setExpression(expression)
.build();
// Define the single access boundary rule using the above properties.
CredentialAccessBoundary.AccessBoundaryRulerule=
CredentialAccessBoundary.AccessBoundaryRule.newBuilder()
.setAvailableResource(availableResource)
.addAvailablePermission(availablePermission)
.setAvailabilityCondition(availabilityCondition)
.build();
// Define the Credential Access Boundary with all the relevant rules.
CredentialAccessBoundarycredentialAccessBoundary=
CredentialAccessBoundary.newBuilder().addRule(rule).build();
// Create the downscoped credentials.
DownscopedCredentialsdownscopedCredentials=
DownscopedCredentials.newBuilder()
.setSourceCredential(sourceCredentials)
.setCredentialAccessBoundary(credentialAccessBoundary)
.build();
// Retrieve the token.
// This will need to be passed to the Token Consumer.
AccessTokenaccessToken=downscopedCredentials.refreshAccessToken();
returnaccessToken;
}The following example shows how a token consumer can use a refresh handler to automatically obtain and refresh downscoped tokens:
publicstaticvoidtokenConsumer(finalStringbucketName,finalStringobjectName)
throwsIOException{
// You can pass an `OAuth2RefreshHandler` to `OAuth2CredentialsWithRefresh` which will allow the
// library to seamlessly handle downscoped token refreshes on expiration.
OAuth2CredentialsWithRefresh.OAuth2RefreshHandlerhandler=
newOAuth2CredentialsWithRefresh.OAuth2RefreshHandler(){
@Override
publicAccessTokenrefreshAccessToken()throwsIOException{
// The common pattern of usage is to have a token broker pass the downscoped short-lived
// access tokens to a token consumer via some secure authenticated channel.
// For illustration purposes, we are generating the downscoped token locally.
// We want to test the ability to limit access to objects with a certain prefix string
// in the resource bucket. objectName.substring(0, 3) is the prefix here. This field is
// not required if access to all bucket resources are allowed. If access to limited
// resources in the bucket is needed, this mechanism can be used.
returngetTokenFromBroker(bucketName,objectName.substring(0,3));
}
};
// Downscoped token retrieved from token broker.
AccessTokendownscopedToken=handler.refreshAccessToken();
// Create the OAuth2CredentialsWithRefresh from the downscoped token and pass a refresh handler
// which will handle token expiration.
// This will allow the consumer to seamlessly obtain new downscoped tokens on demand every time
// token expires.
OAuth2CredentialsWithRefreshcredentials=
OAuth2CredentialsWithRefresh.newBuilder()
.setAccessToken(downscopedToken)
.setRefreshHandler(handler)
.build();
// Use the credentials with the Cloud Storage SDK.
StorageOptionsoptions=StorageOptions.newBuilder().setCredentials(credentials).build();
Storagestorage=options.getService();
// Call Cloud Storage APIs.
Blobblob=storage.get(bucketName,objectName);
Stringcontent=newString(blob.getContent());
System.out.println(
"Retrieved object, "
+objectName
+", from bucket,"
+bucketName
+", with content: "
+content);
}Node.js
For Node.js, you can exchange and refresh tokens automatically with version
7.9.0 or later of the google-auth-library package.
To check your package version, run the following command in your application directory:
npmlistgoogle-auth-library
The following example shows how a token broker can generate downscoped tokens:
// Imports the Google Auth libraries.
const{GoogleAuth,DownscopedClient}=require('google-auth-library');
/**
* Simulates token broker generating downscoped tokens for specified bucket.
*
* @param bucketName The name of the Cloud Storage bucket.
* @param objectPrefix The prefix string of the object name. This is used
* to ensure access is restricted to only objects starting with this
* prefix string.
*/
asyncfunctiongetTokenFromBroker(bucketName,objectPrefix){
constgoogleAuth=newGoogleAuth ({
scopes:'https://www.googleapis.com/auth/cloud-platform',
});
// Define the Credential Access Boundary object.
constcab={
// Define the access boundary.
accessBoundary:{
// Define the single access boundary rule.
accessBoundaryRules:[
{
availableResource:`//storage.googleapis.com/projects/_/buckets/${bucketName}`,
// Downscoped credentials will have readonly access to the resource.
availablePermissions:['inRole:roles/storage.objectViewer'],
// Only objects starting with the specified prefix string in the object name
// will be allowed read access.
availabilityCondition:{
expression:
"resource.name.startsWith('projects/_/buckets/"+
`${bucketName}/objects/${objectPrefix}')`,
},
},
],
},
};
// Obtain an authenticated client via ADC.
constclient=awaitgoogleAuth.getClient ();
// Use the client to create a DownscopedClient.
constcabClient=newDownscopedClient (client,cab);
// Refresh the tokens.
constrefreshedAccessToken=awaitcabClient.getAccessToken();
// This will need to be passed to the token consumer.
returnrefreshedAccessToken;
}The following example shows how a token consumer can provide a refresh handler that automatically obtains and refreshes downscoped tokens:
// Imports the Google Auth and Google Cloud libraries.
const{OAuth2Client}=require('google-auth-library');
const{Storage}=require('@google-cloud/storage');
/**
* Simulates token consumer generating calling GCS APIs using generated
* downscoped tokens for specified bucket.
*
* @param bucketName The name of the Cloud Storage bucket.
* @param objectName The name of the object in the Cloud Storage bucket
* to read.
*/
asyncfunctiontokenConsumer(bucketName,objectName){
// Create the OAuth credentials (the consumer).
constoauth2Client=newOAuth2Client ();
// We are defining a refresh handler instead of a one-time access
// token/expiry pair.
// This will allow the consumer to obtain new downscoped tokens on
// demand every time a token is expired, without any additional code
// changes.
oauth2Client.refreshHandler =async()=>{
// The common pattern of usage is to have a token broker pass the
// downscoped short-lived access tokens to a token consumer via some
// secure authenticated channel. For illustration purposes, we are
// generating the downscoped token locally. We want to test the ability
// to limit access to objects with a certain prefix string in the
// resource bucket. objectName.substring(0, 3) is the prefix here. This
// field is not required if access to all bucket resources are allowed.
// If access to limited resources in the bucket is needed, this mechanism
// can be used.
constrefreshedAccessToken=awaitgetTokenFromBroker(
bucketName,
objectName.substring(0,3)
);
return{
access_token:refreshedAccessToken.token,
expiry_date:refreshedAccessToken.expirationTime,
};
};
conststorageOptions={
projectId:process.env.GOOGLE_CLOUD_PROJECT,
authClient:oauth2Client,
};
conststorage=newStorage(storageOptions);
constdownloadFile=awaitstorage
.bucket(bucketName)
.file(objectName)
.download ();
console.log(downloadFile.toString('utf8'));
}Python
For Python, you can exchange and refresh tokens automatically with version 2.0.0
or later of the google-auth package.
To check your package version, run the following command in the environment where the package is installed:
pipshowgoogle-auth
The following example shows how a token broker can generate downscoped tokens:
importgoogle.auth
fromgoogle.authimport downscoped
fromgoogle.auth.transportimport requests
defget_token_from_broker(bucket_name, object_prefix):
"""Simulates token broker generating downscoped tokens for specified bucket.
Args:
bucket_name (str): The name of the Cloud Storage bucket.
object_prefix (str): The prefix string of the object name. This is used
to ensure access is restricted to only objects starting with this
prefix string.
Returns:
Tuple[str, datetime.datetime]: The downscoped access token and its expiry date.
"""
# Initialize the Credential Access Boundary rules.
available_resource = f"//storage.googleapis.com/projects/_/buckets/{bucket_name}"
# Downscoped credentials will have readonly access to the resource.
available_permissions = ["inRole:roles/storage.objectViewer"]
# Only objects starting with the specified prefix string in the object name
# will be allowed read access.
availability_expression = (
"resource.name.startsWith('projects/_/buckets/{}/objects/{}')".format(
bucket_name, object_prefix
)
)
availability_condition = downscoped.AvailabilityCondition(availability_expression)
# Define the single access boundary rule using the above properties.
rule = downscoped.AccessBoundaryRule(
available_resource=available_resource,
available_permissions=available_permissions,
availability_condition=availability_condition,
)
# Define the Credential Access Boundary with all the relevant rules.
credential_access_boundary = downscoped.CredentialAccessBoundary(rules=[rule])
# Retrieve the source credentials via ADC.
source_credentials, _ = google.auth.default()
if source_credentials.requires_scopes:
source_credentials = source_credentials.with_scopes(
["https://www.googleapis.com/auth/cloud-platform"]
)
# Create the downscoped credentials.
downscoped_credentials = downscoped.Credentials(
source_credentials=source_credentials,
credential_access_boundary=credential_access_boundary,
)
# Refresh the tokens.
downscoped_credentials.refresh(requests.Request())
# These values will need to be passed to the token consumer.
access_token = downscoped_credentials.token
expiry = downscoped_credentials.expiry
return (access_token, expiry)
The following example shows how a token consumer can provide a refresh handler that automatically obtains and refreshes downscoped tokens:
fromgoogle.cloudimport storage
fromgoogle.oauth2import credentials
deftoken_consumer(bucket_name, object_name):
"""Tests token consumer readonly access to the specified object.
Args:
bucket_name (str): The name of the Cloud Storage bucket.
object_name (str): The name of the object in the Cloud Storage bucket
to read.
"""
# Create the OAuth credentials from the downscoped token and pass a
# refresh handler to handle token expiration. We are passing a
# refresh_handler instead of a one-time access token/expiry pair.
# This will allow the consumer to obtain new downscoped tokens on
# demand every time a token is expired, without any additional code
# changes.
defrefresh_handler(request, scopes=None):
# The common pattern of usage is to have a token broker pass the
# downscoped short-lived access tokens to a token consumer via some
# secure authenticated channel.
# For illustration purposes, we are generating the downscoped token
# locally.
# We want to test the ability to limit access to objects with a certain
# prefix string in the resource bucket. object_name[0:3] is the prefix
# here. This field is not required if access to all bucket resources are
# allowed. If access to limited resources in the bucket is needed, this
# mechanism can be used.
return get_token_from_broker(bucket_name, object_prefix=object_name[0:3])
creds = credentials.Credentials(
None,
scopes=["https://www.googleapis.com/auth/cloud-platform"],
refresh_handler=refresh_handler,
)
# Initialize a Cloud Storage client with the oauth2 credentials.
storage_client = storage .Client (credentials=creds)
# The token broker has readonly access to the specified bucket object.
bucket = storage_client.bucket (bucket_name)
blob = bucket.blob(object_name)
print(blob.download_as_bytes ().decode("utf-8"))
Exchange and refresh the access token manually
A token broker can use the Security Token Service API to exchange an access token for a downscoped access token. It can then provide the downscoped token to a token consumer.
To exchange the access token, use the following HTTP method and URL:
POST https://sts.googleapis.com/v1/token
Set the Content-Type header in the request to
application/x-www-form-urlencoded. Include the following fields in the request
body:
| Fields | |
|---|---|
grant_type |
Use the value
|
options |
A JSON-formatted Credential Access Boundary, encoded with percent encoding. |
requested_token_type |
Use the value
|
subject_token |
The OAuth 2.0 access token that you want to exchange. |
subject_token_type |
Use the value
|
The response is a JSON object that contains the following fields:
| Fields | |
|---|---|
access_token |
A downscoped OAuth 2.0 access token that respects the Credential Access Boundary. |
expires_in |
The amount of time until the downscoped token expires, in seconds. This field is included only if the original access token represents a service account. When this field is not included, the downscoped token has the same time to expire as the original access token. |
issued_token_type |
Contains the value
|
token_type |
Contains the value |
For example, if a JSON-formatted Credential Access Boundary is stored in the file
./access-boundary.json, you can use the following
curl command to exchange the access token. Replace
original-token with the original access token:
curl-H"Content-Type:application/x-www-form-urlencoded"\ -XPOST\ https://sts.googleapis.com/v1/token\ -d"grant_type=urn:ietf:params:oauth:grant-type:token-exchange&subject_token_type=urn:ietf:params:oauth:token-type:access_token&requested_token_type=urn:ietf:params:oauth:token-type:access_token&subject_token=original-token"\ --data-urlencode"options=$(cat./access-boundary.json)"
The response is similar to the following example:
{
"access_token":"ya29.dr.AbCDeFg-123456...",
"issued_token_type":"urn:ietf:params:oauth:token-type:access_token",
"token_type":"Bearer",
"expires_in":3600
}
When a token consumer requests a downscoped token, the token broker responds with both the downscoped token and the number of seconds until it expires. If the token has expired, the server rejects the request. To refresh the downscoped token, the consumer can request a downscoped token from the broker before the existing token expires.
What's next
- Learn about access control for Cloud Storage.
- Create a short-lived service account credential.
- Create an OAuth 2.0 access token for a service account with one of the following methods:
- Create an OAuth 2.0 access token for a user.
- See the permissions in each predefined role.
- Learn about custom roles.