Authenticate workloads with Google Cloud auth libraries

This document shows you how to use Workload Identity Federation with Google Cloud authentication libraries, known as auth libraries, to authenticate workloads from third-party identity providers such as AWS, Microsoft Azure, and providers that support OpenID Connect (OIDC) or SAML 2.0.

Workload Identity Federation lets applications running outside Google Cloud access Google Cloud resources without using service account keys. The Google auth libraries enable this by exchanging external credentials for short-lived Google Cloud access tokens.

For authentication, you can obtain external credentials using the following methods:

Before you begin

  1. Enable the required APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role ( roles/serviceusage.serviceUsageAdmin ), which contains the serviceusage.services.enable permission. Learn how to grant roles .

    Enable the APIs

  2. Configure Workload Identity Federation with your identity provider.

Authenticate using the standard credential mechanism

For commonly supported third-party identity providers, you can use the Google Cloud auth library's built-in capabilities to authenticate your workloads by generating a credential configuration file. This file provides the necessary information for the auth libraries to federate identities from external providers.

The credential configuration file, typically loaded using the GOOGLE_APPLICATION_CREDENTIALS environment variable, can instruct the auth libraries to obtain the third-party subject token using one of the following methods:

  • File-sourced:The library reads the subject token from a local file. A separate process must ensure this file contains a valid, unexpired token.
  • URL-sourced:The library fetches the subject token by making a request to a specified local URL endpoint.
  • Executable-sourced:The library runs a configured executable command. The standard output of the executable is expected to contain the subject token.
  1. Generate the credential configuration file for your specific provider:

  2. Use the credential configuration file to authenticate.

    To let the Google Cloud client libraries automatically locate and use your credential configuration file, set the GOOGLE_APPLICATION_CREDENTIALS environment variable to the path of the generated JSON file.

    Export the environment variable in your shell: bash export GOOGLE_APPLICATION_CREDENTIALS=/path/to/your/config.json

    After you set the environment variable, the client libraries handle the authentication flow.

The following code sample shows how to make an authenticated call to a Google Cloud API:

Node.js

To learn how to install and use the client library for IAM, see IAM client libraries . For more information, see the IAM Node.js API reference documentation .

To authenticate to IAM, set up Application Default Credentials. For more information, see Set up authentication for a local development environment .

  // Imports the Google Cloud client library. 
 const 
  
 { 
 Storage 
 } 
  
 = 
  
 require 
 ( 
 ' @google-cloud/storage 
' 
 ); 
 // Instantiates a client. If you don't specify credentials when constructing 
 // the client, the client library will look for credentials in the 
 // environment. 
 const 
  
 storage 
  
 = 
  
 new 
  
  Storage 
 
 (); 
 // Makes an authenticated API request. 
 async 
  
 function 
  
 listBuckets 
 () 
  
 { 
  
 try 
  
 { 
  
 const 
  
 results 
  
 = 
  
 await 
  
 storage 
 . 
  getBuckets 
 
 (); 
  
 const 
  
 [ 
 buckets 
 ] 
  
 = 
  
 results 
 ; 
  
 console 
 . 
 log 
 ( 
 'Buckets:' 
 ); 
  
 buckets 
 . 
 forEach 
 ( 
 bucket 
  
 = 
>  
 { 
  
 console 
 . 
 log 
 ( 
 bucket 
 . 
 name 
 ); 
  
 }); 
  
 } 
  
 catch 
  
 ( 
 err 
 ) 
  
 { 
  
 console 
 . 
 error 
 ( 
 'ERROR:' 
 , 
  
 err 
 ); 
  
 } 
 } 
 listBuckets 
 (); 
 

Python

To learn how to install and use the client library for IAM, see IAM client libraries . For more information, see the IAM Python API reference documentation .

To authenticate to IAM, set up Application Default Credentials. For more information, see Set up authentication for a local development environment .

  def 
  
 implicit 
 (): 
 from 
  
 google.cloud 
  
 import 
  storage 
 
 # If you don't specify credentials when constructing the client, the 
 # client library will look for credentials in the environment. 
 storage_client 
 = 
  storage 
 
 . 
  Client 
 
 () 
 # Make an authenticated API request 
 buckets 
 = 
 list 
 ( 
 storage_client 
 . 
  list_buckets 
 
 ()) 
 print 
 ( 
 buckets 
 ) 
 

Java

To learn how to install and use the client library for IAM, see IAM client libraries . For more information, see the IAM Java API reference documentation .

To authenticate to IAM, set up Application Default Credentials. For more information, see Set up authentication for a local development environment .

  static 
  
 void 
  
 authImplicit 
 () 
  
 { 
  
 // If you don't specify credentials when constructing the client, the client library will 
  
 // look for credentials via the environment variable GOOGLE_APPLICATION_CREDENTIALS. 
  
 Storage 
  
 storage 
  
 = 
  
 StorageOptions 
 . 
 getDefaultInstance 
 (). 
 getService 
 (); 
  
 System 
 . 
 out 
 . 
 println 
 ( 
 "Buckets:" 
 ); 
  
 Page<Bucket> 
  
 buckets 
  
 = 
  
 storage 
 . 
 list 
 (); 
  
 for 
  
 ( 
 Bucket 
  
 bucket 
  
 : 
  
 buckets 
 . 
 iterateAll 
 ()) 
  
 { 
  
 System 
 . 
 out 
 . 
 println 
 ( 
 bucket 
 . 
 toString 
 ()); 
  
 } 
 } 
 

Go

To learn how to install and use the client library for IAM, see IAM client libraries . For more information, see the IAM Go API reference documentation .

To authenticate to IAM, set up Application Default Credentials. For more information, see Set up authentication for a local development environment .

  import 
  
 ( 
  
 "context" 
  
 "fmt" 
  
 "io" 
  
 "cloud.google.com/go/storage" 
  
 "google.golang.org/api/iterator" 
 ) 
 // authenticateImplicitWithAdc uses Application Default Credentials 
 // to automatically find credentials and authenticate. 
 func 
  
 authenticateImplicitWithAdc 
 ( 
 w 
  
 io 
 . 
  Writer 
 
 , 
  
 projectId 
  
 string 
 ) 
  
 error 
  
 { 
  
 // projectId := "your_project_id" 
  
 ctx 
  
 := 
  
 context 
 . 
 Background 
 () 
  
 // NOTE: Replace the client created below with the client required for your application. 
  
 // Note that the credentials are not specified when constructing the client. 
  
 // The client library finds your credentials using ADC. 
  
 client 
 , 
  
 err 
  
 := 
  
 storage 
 . 
 NewClient 
 ( 
 ctx 
 ) 
  
 if 
  
 err 
  
 != 
  
 nil 
  
 { 
  
 return 
  
 fmt 
 . 
 Errorf 
 ( 
 "NewClient: %w" 
 , 
  
 err 
 ) 
  
 } 
  
 defer 
  
 client 
 . 
 Close 
 () 
  
 it 
  
 := 
  
 client 
 . 
  Buckets 
 
 ( 
 ctx 
 , 
  
 projectId 
 ) 
  
 for 
  
 { 
  
 bucketAttrs 
 , 
  
 err 
  
 := 
  
 it 
 . 
 Next 
 () 
  
 if 
  
 err 
  
 == 
  
 iterator 
 . 
 Done 
  
 { 
  
 break 
  
 } 
  
 if 
  
 err 
  
 != 
  
 nil 
  
 { 
  
 return 
  
 err 
  
 } 
  
 fmt 
 . 
 Fprintf 
 ( 
 w 
 , 
  
 "Bucket: %v\n" 
 , 
  
 bucketAttrs 
 . 
 Name 
 ) 
  
 } 
  
 fmt 
 . 
 Fprintf 
 ( 
 w 
 , 
  
 "Listed all storage buckets.\n" 
 ) 
  
 return 
  
 nil 
 } 
 

Authenticate using custom credential suppliers

If your environment doesn't support the built-in capabilities of the Google auth library or if you want to implement custom logic to supply credentials to the Google auth library, use custom credential suppliers to authenticate your workloads.

Access resources from AWS

When you initialize the authentication client, provide a custom implementation of a credential supplier. The client instance defers to the supplier to retrieve AWS security credentials to exchange for a Google Cloud access token. The supplier must return valid and unexpired credentials when the client calls it.

The authentication client doesn't cache the returned AWS security credentials or region, so implement caching in the supplier to prevent redundant requests for the same resources.

The following code samples show how you can set up access to Google Cloud resources from AWS with a custom credential supplier.

Node.js

To learn how to install and use the client library for IAM, see IAM client libraries . For more information, see the IAM Node.js API reference documentation .

To authenticate to IAM, set up Application Default Credentials. For more information, see Set up authentication for a local development environment .

  const 
  
 { 
 AwsClient 
 } 
  
 = 
  
 require 
 ( 
 ' google-auth-library 
' 
 ); 
 const 
  
 { 
 fromNodeProviderChain 
 } 
  
 = 
  
 require 
 ( 
 '@aws-sdk/credential-providers' 
 ); 
 const 
  
 fs 
  
 = 
  
 require 
 ( 
 'fs' 
 ); 
 const 
  
 path 
  
 = 
  
 require 
 ( 
 'path' 
 ); 
 const 
  
 { 
 STSClient 
 } 
  
 = 
  
 require 
 ( 
 '@aws-sdk/client-sts' 
 ); 
 const 
  
 { 
 Storage 
 } 
  
 = 
  
 require 
 ( 
 ' @google-cloud/storage 
' 
 ); 
 /** 
 * Custom AWS Security Credentials Supplier. 
 * 
 * This implementation resolves AWS credentials using the default Node provider 
 * chain from the AWS SDK. This allows fetching credentials from environment 
 * variables, shared credential files (~/.aws/credentials), or IAM roles 
 * for service accounts (IRSA) in EKS, etc. 
 */ 
 class 
  
 CustomAwsSupplier 
  
 { 
  
 constructor 
 () 
  
 { 
  
 this 
 . 
 region 
  
 = 
  
 null 
 ; 
  
 this 
 . 
 awsCredentialsProvider 
  
 = 
  
 fromNodeProviderChain 
 (); 
  
 } 
  
 /** 
 * Returns the AWS region. This is required for signing the AWS request. 
 * It resolves the region automatically by using the default AWS region 
 * provider chain, which searches for the region in the standard locations 
 * (environment variables, AWS config file, etc.). 
 */ 
  
 async 
  
 getAwsRegion 
 ( 
 _context 
 ) 
  
 { 
  
 if 
  
 ( 
 this 
 . 
 region 
 ) 
  
 { 
  
 return 
  
 this 
 . 
 region 
 ; 
  
 } 
  
 const 
  
 client 
  
 = 
  
 new 
  
 STSClient 
 ({}); 
  
 this 
 . 
 region 
  
 = 
  
 await 
  
 client 
 . 
 config 
 . 
 region 
 (); 
  
 if 
  
 ( 
 ! 
 this 
 . 
 region 
 ) 
  
 { 
  
 throw 
  
 new 
  
 Error 
 ( 
  
 'CustomAwsSupplier: Unable to resolve AWS region. Please set the AWS_REGION environment variable or configure it in your ~/.aws/config file.' 
  
 ); 
  
 } 
  
 return 
  
 this 
 . 
 region 
 ; 
  
 } 
  
 /** 
 * Retrieves AWS security credentials using the AWS SDK's default provider chain. 
 */ 
  
 async 
  
 getAwsSecurityCredentials 
 ( 
 _context 
 ) 
  
 { 
  
 const 
  
 awsCredentials 
  
 = 
  
 await 
  
 this 
 . 
 awsCredentialsProvider 
 (); 
  
 if 
  
 ( 
 ! 
 awsCredentials 
 . 
 accessKeyId 
  
 || 
  
 ! 
 awsCredentials 
 . 
 secretAccessKey 
 ) 
  
 { 
  
 throw 
  
 new 
  
 Error 
 ( 
  
 'Unable to resolve AWS credentials from the node provider chain. ' 
  
 + 
  
 'Ensure your AWS CLI is configured, or AWS environment variables (like AWS_ACCESS_KEY_ID) are set.' 
  
 ); 
  
 } 
  
 return 
  
 { 
  
 accessKeyId 
 : 
  
 awsCredentials 
 . 
 accessKeyId 
 , 
  
 secretAccessKey 
 : 
  
 awsCredentials 
 . 
 secretAccessKey 
 , 
  
 token 
 : 
  
 awsCredentials 
 . 
 sessionToken 
 , 
  
 }; 
  
 } 
 } 
 /** 
 * Authenticates with Google Cloud using AWS credentials and retrieves bucket metadata. 
 * 
 * @param {string} bucketName The name of the bucket to retrieve. 
 * @param {string} audience The Workload Identity Pool audience. 
 * @param {string} [impersonationUrl] Optional Service Account impersonation URL. 
 */ 
 async 
  
 function 
  
 authenticateWithAwsCredentials 
 ( 
  
 bucketName 
 , 
  
 audience 
 , 
  
 impersonationUrl 
 ) 
  
 { 
  
 const 
  
 customSupplier 
  
 = 
  
 new 
  
 CustomAwsSupplier 
 (); 
  
 const 
  
 clientOptions 
  
 = 
  
 { 
  
 audience 
 : 
  
 audience 
 , 
  
 subject_token_type 
 : 
  
 'urn:ietf:params:aws:token-type:aws4_request' 
 , 
  
 service_account_impersonation_url 
 : 
  
 impersonationUrl 
 , 
  
 aws_security_credentials_supplier 
 : 
  
 customSupplier 
 , 
  
 }; 
  
 const 
  
 authClient 
  
 = 
  
 new 
  
  AwsClient 
 
 ( 
 clientOptions 
 ); 
  
 const 
  
 storage 
  
 = 
  
 new 
  
  Storage 
 
 ({ 
  
 authClient 
 : 
  
 authClient 
 , 
  
 }); 
  
 const 
  
 [ 
 metadata 
 ] 
  
 = 
  
 await 
  
 storage 
 . 
 bucket 
 ( 
 bucketName 
 ). 
 getMetadata 
 (); 
  
 return 
  
 metadata 
 ; 
 } 
 

Python

To learn how to install and use the client library for IAM, see IAM client libraries . For more information, see the IAM Python API reference documentation .

To authenticate to IAM, set up Application Default Credentials. For more information, see Set up authentication for a local development environment .

  import 
  
 json 
 import 
  
 os 
 import 
  
 sys 
 import 
  
 boto3 
 from 
  
 google.auth 
  
 import 
 aws 
 from 
  
 google.auth 
  
 import 
  exceptions 
 
 from 
  
 google.cloud 
  
 import 
  storage 
 
 class 
  
 CustomAwsSupplier 
 ( 
 aws 
 . 
 AwsSecurityCredentialsSupplier 
 ): 
  
 """Custom AWS Security Credentials Supplier using Boto3.""" 
 def 
  
 __init__ 
 ( 
 self 
 ): 
  
 """Initializes the Boto3 session, prioritizing environment variables for region.""" 
 # Explicitly read the region from the environment first. 
 region 
 = 
 os 
 . 
 getenv 
 ( 
 "AWS_REGION" 
 ) 
 or 
 os 
 . 
 getenv 
 ( 
 "AWS_DEFAULT_REGION" 
 ) 
 # If region is None, Boto3's discovery chain will be used when needed. 
 self 
 . 
 session 
 = 
 boto3 
 . 
 Session 
 ( 
 region_name 
 = 
 region 
 ) 
 self 
 . 
 _cached_region 
 = 
 None 
 def 
  
 get_aws_region 
 ( 
 self 
 , 
 context 
 , 
 request 
 ) 
 - 
> str 
 : 
  
 """Returns the AWS region using Boto3's default provider chain.""" 
 if 
 self 
 . 
 _cached_region 
 : 
 return 
 self 
 . 
 _cached_region 
 self 
 . 
 _cached_region 
 = 
 self 
 . 
 session 
 . 
 region_name 
 if 
 not 
 self 
 . 
 _cached_region 
 : 
 raise 
  exceptions 
 
 . 
 GoogleAuthError 
 ( 
 "Boto3 was unable to resolve an AWS region." 
 ) 
 return 
 self 
 . 
 _cached_region 
 def 
  
 get_aws_security_credentials 
 ( 
 self 
 , 
 context 
 , 
 request 
 = 
 None 
 ) 
 - 
> aws 
 . 
 AwsSecurityCredentials 
 : 
  
 """Retrieves AWS security credentials using Boto3's default provider chain.""" 
 creds 
 = 
 self 
 . 
 session 
 . 
 get_credentials 
 () 
 if 
 not 
 creds 
 : 
 raise 
  exceptions 
 
 . 
 GoogleAuthError 
 ( 
 "Unable to resolve AWS credentials from Boto3." 
 ) 
 return 
 aws 
 . 
 AwsSecurityCredentials 
 ( 
 access_key_id 
 = 
 creds 
 . 
 access_key 
 , 
 secret_access_key 
 = 
 creds 
 . 
 secret_key 
 , 
 session_token 
 = 
 creds 
 . 
 token 
 , 
 ) 
 def 
  
 authenticate_with_aws_credentials 
 ( 
 bucket_name 
 , 
 audience 
 , 
 impersonation_url 
 = 
 None 
 ): 
  
 """Authenticates using the custom AWS supplier and gets bucket metadata. 
 Returns: 
 dict: The bucket metadata response from the Google Cloud Storage API. 
 """ 
 custom_supplier 
 = 
 CustomAwsSupplier 
 () 
 credentials 
 = 
 aws 
 . 
 Credentials 
 ( 
 audience 
 = 
 audience 
 , 
 subject_token_type 
 = 
 "urn:ietf:params:aws:token-type:aws4_request" 
 , 
 service_account_impersonation_url 
 = 
 impersonation_url 
 , 
 aws_security_credentials_supplier 
 = 
 custom_supplier 
 , 
 scopes 
 = 
 [ 
 "https://www.googleapis.com/auth/devstorage.read_only" 
 ], 
 ) 
 storage_client 
 = 
  storage 
 
 . 
  Client 
 
 ( 
 credentials 
 = 
 credentials 
 ) 
 bucket 
 = 
 storage_client 
 . 
  get_bucket 
 
 ( 
 bucket_name 
 ) 
 return 
 bucket 
 . 
 _properties 
 

Java

To learn how to install and use the client library for IAM, see IAM client libraries . For more information, see the IAM Java API reference documentation .

To authenticate to IAM, set up Application Default Credentials. For more information, see Set up authentication for a local development environment .

  import 
  
 com.google.auth.oauth2. AwsCredentials 
 
 ; 
 import 
  
 com.google.auth.oauth2. AwsSecurityCredentials 
 
 ; 
 import 
  
 com.google.auth.oauth2. AwsSecurityCredentialsSupplier 
 
 ; 
 import 
  
 com.google.auth.oauth2. ExternalAccountSupplierContext 
 
 ; 
 import 
  
 com.google.auth.oauth2. GoogleCredentials 
 
 ; 
 import 
  
 com.google.cloud.storage. Bucket 
 
 ; 
 import 
  
 com.google.cloud.storage. Storage 
 
 ; 
 import 
  
 com.google.cloud.storage. StorageOptions 
 
 ; 
 import 
  
 com.google.gson.Gson 
 ; 
 import 
  
 com.google.gson.reflect.TypeToken 
 ; 
 import 
  
 java.io.IOException 
 ; 
 import 
  
 java.io.Reader 
 ; 
 import 
  
 java.lang.reflect.Type 
 ; 
 import 
  
 java.nio.file.Files 
 ; 
 import 
  
 java.nio.file.Paths 
 ; 
 import 
  
 java.util.Map 
 ; 
 import 
  
 software.amazon.awssdk.auth.credentials.AwsCredentialsProvider 
 ; 
 import 
  
 software.amazon.awssdk.auth.credentials.AwsSessionCredentials 
 ; 
 import 
  
 software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider 
 ; 
 import 
  
 software.amazon.awssdk.regions.Region 
 ; 
 import 
  
 software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain 
 ; 
  
 public 
  
 static 
  
  Bucket 
 
  
 authenticateWithAwsCredentials 
 ( 
  
 String 
  
 gcpWorkloadAudience 
 , 
  
 String 
  
 saImpersonationUrl 
 , 
  
 String 
  
 gcsBucketName 
 ) 
  
 throws 
  
 IOException 
  
 { 
  
 CustomAwsSupplier 
  
 customSupplier 
  
 = 
  
 new 
  
 CustomAwsSupplier 
 (); 
  
  AwsCredentials 
 
 . 
 Builder 
  
 credentialsBuilder 
  
 = 
  
  AwsCredentials 
 
 . 
 newBuilder 
 () 
  
 . 
 setAudience 
 ( 
 gcpWorkloadAudience 
 ) 
  
 // This token type indicates that the subject token is an AWS Signature Version 4 signed 
  
 // request. This is required for AWS Workload Identity Federation. 
  
 . 
 setSubjectTokenType 
 ( 
 "urn:ietf:params:aws:token-type:aws4_request" 
 ) 
  
 . 
  setAwsSecurityCredentialsSupplier 
 
 ( 
 customSupplier 
 ); 
  
 if 
  
 ( 
 saImpersonationUrl 
  
 != 
  
 null 
 ) 
  
 { 
  
 credentialsBuilder 
 . 
 setServiceAccountImpersonationUrl 
 ( 
 saImpersonationUrl 
 ); 
  
 } 
  
  GoogleCredentials 
 
  
 credentials 
  
 = 
  
 credentialsBuilder 
 . 
 build 
 (); 
  
  Storage 
 
  
 storage 
  
 = 
  
  StorageOptions 
 
 . 
 newBuilder 
 (). 
 setCredentials 
 ( 
 credentials 
 ). 
 build 
 (). 
  getService 
 
 (); 
  
 return 
  
 storage 
 . 
  get 
 
 ( 
 gcsBucketName 
 ); 
  
 } 
  
 /** 
 * Custom AWS Security Credentials Supplier. 
 * 
 * <p>This implementation resolves AWS credentials and regions using the default provider chains 
 * from the AWS SDK (v2). This supports environment variables, ~/.aws/credentials, and EC2/EKS 
 * metadata. 
 */ 
  
 private 
  
 static 
  
 class 
 CustomAwsSupplier 
  
 implements 
  
  AwsSecurityCredentialsSupplier 
 
  
 { 
  
 private 
  
 final 
  
 AwsCredentialsProvider 
  
 awsCredentialsProvider 
 ; 
  
 private 
  
 String 
  
 region 
 ; 
  
 public 
  
 CustomAwsSupplier 
 () 
  
 { 
  
 // The AWS SDK handles caching internally. 
  
 this 
 . 
 awsCredentialsProvider 
  
 = 
  
 DefaultCredentialsProvider 
 . 
 create 
 (); 
  
 } 
  
 @Override 
  
 public 
  
 String 
  
 getRegion 
 ( 
  ExternalAccountSupplierContext 
 
  
 context 
 ) 
  
 { 
  
 if 
  
 ( 
 this 
 . 
 region 
  
 == 
  
 null 
 ) 
  
 { 
  
 Region 
  
 awsRegion 
  
 = 
  
 new 
  
 DefaultAwsRegionProviderChain 
 (). 
  getRegion 
 
 (); 
  
 if 
  
 ( 
 awsRegion 
  
 == 
  
 null 
 ) 
  
 { 
  
 throw 
  
 new 
  
 IllegalStateException 
 ( 
  
 "Unable to resolve AWS region. Ensure AWS_REGION is set or configured." 
 ); 
  
 } 
  
 this 
 . 
 region 
  
 = 
  
 awsRegion 
 . 
 id 
 (); 
  
 } 
  
 return 
  
 this 
 . 
 region 
 ; 
  
 } 
  
 @Override 
  
 public 
  
  AwsSecurityCredentials 
 
  
 getCredentials 
 ( 
  ExternalAccountSupplierContext 
 
  
 context 
 ) 
  
 { 
  
 software 
 . 
 amazon 
 . 
 awssdk 
 . 
 auth 
 . 
 credentials 
 . 
  AwsCredentials 
 
  
 credentials 
  
 = 
  
 this 
 . 
 awsCredentialsProvider 
 . 
 resolveCredentials 
 (); 
  
 if 
  
 ( 
 credentials 
  
 == 
  
 null 
 ) 
  
 { 
  
 throw 
  
 new 
  
 IllegalStateException 
 ( 
 "Unable to resolve AWS credentials." 
 ); 
  
 } 
  
 String 
  
 sessionToken 
  
 = 
  
 null 
 ; 
  
 if 
  
 ( 
 credentials 
  
 instanceof 
  
 AwsSessionCredentials 
 ) 
  
 { 
  
 sessionToken 
  
 = 
  
 (( 
 AwsSessionCredentials 
 ) 
  
 credentials 
 ). 
 sessionToken 
 (); 
  
 } 
  
 return 
  
 new 
  
  AwsSecurityCredentials 
 
 ( 
  
 credentials 
 . 
 accessKeyId 
 (), 
  
 credentials 
 . 
 secretAccessKey 
 (), 
  
 sessionToken 
 ); 
  
 } 
  
 } 
 

Access resources from OIDC and SAML

When you initialize the authentication client, provide a custom token supplier to provide a subject token that is exchanged for a Google Cloud access token. The supplier must return a valid and unexpired subject token when the client calls it.

The authentication client doesn't cache the returned token, so implement caching in the supplier to prevent redundant requests for the same subject token.

The following code samples show how you can set up access to Google Cloud resources from providers that support OpenID Connect (OIDC) or SAML 2.0 with a custom credential supplier.

Node.js

To learn how to install and use the client library for IAM, see IAM client libraries . For more information, see the IAM Node.js API reference documentation .

To authenticate to IAM, set up Application Default Credentials. For more information, see Set up authentication for a local development environment .

  const 
  
 { 
 IdentityPoolClient 
 } 
  
 = 
  
 require 
 ( 
 ' google-auth-library 
' 
 ); 
 const 
  
 { 
 Storage 
 } 
  
 = 
  
 require 
 ( 
 ' @google-cloud/storage 
' 
 ); 
 const 
  
 { 
 Gaxios 
 } 
  
 = 
  
 require 
 ( 
 ' gaxios 
' 
 ); 
 const 
  
 fs 
  
 = 
  
 require 
 ( 
 'fs' 
 ); 
 const 
  
 path 
  
 = 
  
 require 
 ( 
 'path' 
 ); 
 /** 
 * A custom SubjectTokenSupplier that authenticates with Okta using the 
 * Client Credentials grant flow. 
 */ 
 class 
  
 OktaClientCredentialsSupplier 
  
 { 
  
 constructor 
 ( 
 domain 
 , 
  
 clientId 
 , 
  
 clientSecret 
 ) 
  
 { 
  
 const 
  
 cleanDomain 
  
 = 
  
 domain 
 . 
 endsWith 
 ( 
 '/' 
 ) 
  
 ? 
  
 domain 
 . 
 slice 
 ( 
 0 
 , 
  
 - 
 1 
 ) 
  
 : 
  
 domain 
 ; 
  
 this 
 . 
 oktaTokenUrl 
  
 = 
  
 ` 
 ${ 
 cleanDomain 
 } 
 /oauth2/default/v1/token` 
 ; 
  
 this 
 . 
 clientId 
  
 = 
  
 clientId 
 ; 
  
 this 
 . 
 clientSecret 
  
 = 
  
 clientSecret 
 ; 
  
 this 
 . 
 accessToken 
  
 = 
  
 null 
 ; 
  
 this 
 . 
 expiryTime 
  
 = 
  
 0 
 ; 
  
 this 
 . 
  gaxios 
 
  
 = 
  
 new 
  
  Gaxios 
 
 (); 
  
 } 
  
 /** 
 * Main method called by the auth library. It will fetch a new token if one 
 * is not already cached. 
 * @returns {Promise<string>} A promise that resolves with the Okta Access token. 
 */ 
  
 async 
  
 getSubjectToken 
 () 
  
 { 
  
 const 
  
 isTokenValid 
  
 = 
  
 this 
 . 
 accessToken 
 && 
 Date 
 . 
 now 
 () 
 < 
 this 
 . 
 expiryTime 
  
 - 
  
 60 
  
 * 
  
 1000 
 ; 
  
 if 
  
 ( 
 isTokenValid 
 ) 
  
 { 
  
 return 
  
 this 
 . 
 accessToken 
 ; 
  
 } 
  
 const 
  
 { 
 accessToken 
 , 
  
 expiresIn 
 } 
  
 = 
  
 await 
  
 this 
 . 
 fetchOktaAccessToken 
 (); 
  
 this 
 . 
 accessToken 
  
 = 
  
 accessToken 
 ; 
  
 this 
 . 
 expiryTime 
  
 = 
  
 Date 
 . 
 now 
 () 
  
 + 
  
 expiresIn 
  
 * 
  
 1000 
 ; 
  
 return 
  
 this 
 . 
 accessToken 
 ; 
  
 } 
  
 /** 
 * Performs the Client Credentials grant flow with Okta. 
 */ 
  
 async 
  
 fetchOktaAccessToken 
 () 
  
 { 
  
 const 
  
 params 
  
 = 
  
 new 
  
 URLSearchParams 
 (); 
  
  params 
 
 . 
 append 
 ( 
 'grant_type' 
 , 
  
 'client_credentials' 
 ); 
  
  params 
 
 . 
 append 
 ( 
 'scope' 
 , 
  
 'gcp.test.read' 
 ); 
  
 const 
  
 authHeader 
  
 = 
  
 'Basic ' 
  
 + 
  
 Buffer 
 . 
 from 
 ( 
 ` 
 ${ 
 this 
 . 
 clientId 
 } 
 : 
 ${ 
 this 
 . 
 clientSecret 
 } 
 ` 
 ). 
 toString 
 ( 
 ' base64 
' 
 ); 
  
 try 
  
 { 
  
 const 
  
 response 
  
 = 
  
 await 
  
 this 
 . 
  gaxios 
 
 . 
  request 
 
 ({ 
  
 url 
 : 
  
 this 
 . 
 oktaTokenUrl 
 , 
  
 method 
 : 
  
 'POST' 
 , 
  
 headers 
 : 
  
 { 
  
 Authorization 
 : 
  
 authHeader 
 , 
  
 'Content-Type' 
 : 
  
 'application/x-www-form-urlencoded' 
 , 
  
 }, 
  
 data 
 : 
  
  params 
 
 . 
 toString 
 (), 
  
 }); 
  
 const 
  
 { 
 access_token 
 , 
  
 expires_in 
 } 
  
 = 
  
 response 
 . 
 data 
 ; 
  
 if 
  
 ( 
 access_token 
 && 
 expires_in 
 ) 
  
 { 
  
 return 
  
 { 
 accessToken 
 : 
  
 access_token 
 , 
  
 expiresIn 
 : 
  
 expires_in 
 }; 
  
 } 
  
 else 
  
 { 
  
 throw 
  
 new 
  
 Error 
 ( 
  
 'Access token or expires_in not found in Okta response.' 
  
 ); 
  
 } 
  
 } 
  
 catch 
  
 ( 
 error 
 ) 
  
 { 
  
 throw 
  
 new 
  
 Error 
 ( 
  
 `Failed to authenticate with Okta: 
 ${ 
 error 
 . 
 response 
 ? 
 . 
 data 
  
 || 
  
 error 
 . 
 message 
 } 
 ` 
  
 ); 
  
 } 
  
 } 
 } 
 /** 
 * Authenticates with Google Cloud using Okta credentials and retrieves bucket metadata. 
 * 
 * @param {string} bucketName The name of the bucket to retrieve. 
 * @param {string} audience The Workload Identity Pool audience. 
 * @param {string} domain The Okta domain. 
 * @param {string} clientId The Okta client ID. 
 * @param {string} clientSecret The Okta client secret. 
 * @param {string} [impersonationUrl] Optional Service Account impersonation URL. 
 */ 
 async 
  
 function 
  
 authenticateWithOktaCredentials 
 ( 
  
 bucketName 
 , 
  
 audience 
 , 
  
 domain 
 , 
  
 clientId 
 , 
  
 clientSecret 
 , 
  
 impersonationUrl 
 ) 
  
 { 
  
 const 
  
 oktaSupplier 
  
 = 
  
 new 
  
 OktaClientCredentialsSupplier 
 ( 
  
 domain 
 , 
  
 clientId 
 , 
  
 clientSecret 
  
 ); 
  
 const 
  
 authClient 
  
 = 
  
 new 
  
  IdentityPoolClient 
 
 ({ 
  
 audience 
 : 
  
 audience 
 , 
  
 subject_token_type 
 : 
  
 'urn:ietf:params:oauth:token-type:jwt' 
 , 
  
 token_url 
 : 
  
 'https://sts.googleapis.com/v1/token' 
 , 
  
 subject_token_supplier 
 : 
  
 oktaSupplier 
 , 
  
 service_account_impersonation_url 
 : 
  
 impersonationUrl 
 , 
  
 }); 
  
 const 
  
 storage 
  
 = 
  
 new 
  
  Storage 
 
 ({ 
  
 authClient 
 : 
  
 authClient 
 , 
  
 }); 
  
 const 
  
 [ 
 metadata 
 ] 
  
 = 
  
 await 
  
 storage 
 . 
 bucket 
 ( 
 bucketName 
 ). 
 getMetadata 
 (); 
  
 return 
  
 metadata 
 ; 
 } 
 

Python

To learn how to install and use the client library for IAM, see IAM client libraries . For more information, see the IAM Python API reference documentation .

To authenticate to IAM, set up Application Default Credentials. For more information, see Set up authentication for a local development environment .

  import 
  
 json 
 import 
  
 time 
 import 
  
 urllib.parse 
 from 
  
 google.auth 
  
 import 
 identity_pool 
 from 
  
 google.cloud 
  
 import 
  storage 
 
 import 
  
 requests 
 class 
  
 OktaClientCredentialsSupplier 
 : 
  
 """A custom SubjectTokenSupplier that authenticates with Okta. 
 This supplier uses the Client Credentials grant flow for machine-to-machine 
 (M2M) authentication with Okta. 
 """ 
 def 
  
 __init__ 
 ( 
 self 
 , 
 domain 
 , 
 client_id 
 , 
 client_secret 
 ): 
 self 
 . 
 okta_token_url 
 = 
 f 
 " 
 { 
  domain 
 
 . 
 rstrip 
 ( 
 '/' 
 ) 
 } 
 /oauth2/default/v1/token" 
 self 
 . 
 client_id 
 = 
 client_id 
 self 
 . 
 client_secret 
 = 
 client_secret 
 self 
 . 
 access_token 
 = 
 None 
 self 
 . 
 expiry_time 
 = 
 0 
 def 
  
 get_subject_token 
 ( 
 self 
 , 
 context 
 , 
 request 
 = 
 None 
 ) 
 - 
> str 
 : 
  
 """Fetches a new token if the current one is expired or missing.""" 
 if 
 self 
 . 
 access_token 
 and 
 time 
 . 
 time 
 () 
< self 
 . 
 expiry_time 
 - 
 60 
 : 
 return 
 self 
 . 
 access_token 
 self 
 . 
 _fetch_okta_access_token 
 () 
 return 
 self 
 . 
 access_token 
 def 
  
 _fetch_okta_access_token 
 ( 
 self 
 ): 
  
 """Performs the Client Credentials grant flow with Okta.""" 
 headers 
 = 
 { 
 "Content-Type" 
 : 
 "application/x-www-form-urlencoded" 
 , 
 "Accept" 
 : 
 "application/json" 
 , 
 } 
 data 
 = 
 { 
 "grant_type" 
 : 
 "client_credentials" 
 , 
 "scope" 
 : 
 "gcp.test.read" 
 , 
 # Set scope as per Okta app config. 
 } 
 response 
 = 
 requests 
 . 
 post 
 ( 
 self 
 . 
 okta_token_url 
 , 
 headers 
 = 
 headers 
 , 
 data 
 = 
 urllib 
 . 
 parse 
 . 
 urlencode 
 ( 
 data 
 ), 
 auth 
 = 
 ( 
 self 
 . 
 client_id 
 , 
 self 
 . 
 client_secret 
 ), 
 ) 
 response 
 . 
 raise_for_status 
 () 
 token_data 
 = 
 response 
 . 
 json 
 () 
 self 
 . 
 access_token 
 = 
 token_data 
 [ 
 "access_token" 
 ] 
 self 
 . 
 expiry_time 
 = 
 time 
 . 
 time 
 () 
 + 
 token_data 
 [ 
 "expires_in" 
 ] 
 def 
  
 authenticate_with_okta_credentials 
 ( 
 bucket_name 
 , 
 audience 
 , 
 domain 
 , 
 client_id 
 , 
 client_secret 
 , 
 impersonation_url 
 = 
 None 
 ): 
  
 """Authenticates using the custom Okta supplier and gets bucket metadata. 
 Returns: 
 dict: The bucket metadata response from the Google Cloud Storage API. 
 """ 
 okta_supplier 
 = 
 OktaClientCredentialsSupplier 
 ( 
 domain 
 , 
 client_id 
 , 
 client_secret 
 ) 
 credentials 
 = 
 identity_pool 
 . 
 Credentials 
 ( 
 audience 
 = 
 audience 
 , 
 subject_token_type 
 = 
 "urn:ietf:params:oauth:token-type:jwt" 
 , 
 token_url 
 = 
 "https://sts.googleapis.com/v1/token" 
 , 
 subject_token_supplier 
 = 
 okta_supplier 
 , 
 default_scopes 
 = 
 [ 
 "https://www.googleapis.com/auth/devstorage.read_only" 
 ], 
 service_account_impersonation_url 
 = 
 impersonation_url 
 , 
 ) 
 storage_client 
 = 
  storage 
 
 . 
  Client 
 
 ( 
 credentials 
 = 
 credentials 
 ) 
 bucket 
 = 
 storage_client 
 . 
  get_bucket 
 
 ( 
 bucket_name 
 ) 
 return 
 bucket 
 . 
 _properties 
 

Java

To learn how to install and use the client library for IAM, see IAM client libraries . For more information, see the IAM Java API reference documentation .

To authenticate to IAM, set up Application Default Credentials. For more information, see Set up authentication for a local development environment .

  import 
  
 com.google.api.client.json. GenericJson 
 
 ; 
 import 
  
 com.google.api.client.json.gson. GsonFactory 
 
 ; 
 import 
  
 com.google.auth.oauth2. ExternalAccountSupplierContext 
 
 ; 
 import 
  
 com.google.auth.oauth2. GoogleCredentials 
 
 ; 
 import 
  
 com.google.auth.oauth2. IdentityPoolCredentials 
 
 ; 
 import 
  
 com.google.auth.oauth2. IdentityPoolSubjectTokenSupplier 
 
 ; 
 import 
  
 com.google.cloud.storage. Bucket 
 
 ; 
 import 
  
 com.google.cloud.storage. Storage 
 
 ; 
 import 
  
 com.google.cloud.storage. StorageOptions 
 
 ; 
 import 
  
 com.google.gson.Gson 
 ; 
 import 
  
 com.google.gson.JsonSyntaxException 
 ; 
 import 
  
 com.google.gson.reflect.TypeToken 
 ; 
 import 
  
 java.io.BufferedReader 
 ; 
 import 
  
 java.io.DataOutputStream 
 ; 
 import 
  
 java.io.IOException 
 ; 
 import 
  
 java.io.InputStreamReader 
 ; 
 import 
  
 java.io.Reader 
 ; 
 import 
  
 java.lang.reflect.Type 
 ; 
 import 
  
 java.net.HttpURLConnection 
 ; 
 import 
  
 java.net.URL 
 ; 
 import 
  
 java.nio.charset.StandardCharsets 
 ; 
 import 
  
 java.nio.file.Files 
 ; 
 import 
  
 java.nio.file.Paths 
 ; 
 import 
  
 java.time.Instant 
 ; 
 import 
  
 java.util.Base64 
 ; 
 import 
  
 java.util.Map 
 ; 
  
 public 
  
 static 
  
  Bucket 
 
  
 authenticateWithOktaCredentials 
 ( 
  
 String 
  
 gcpWorkloadAudience 
 , 
  
 String 
  
 saImpersonationUrl 
 , 
  
 String 
  
 gcsBucketName 
 , 
  
 String 
  
 oktaDomain 
 , 
  
 String 
  
 oktaClientId 
 , 
  
 String 
  
 oktaClientSecret 
 ) 
  
 throws 
  
 IOException 
  
 { 
  
 OktaClientCredentialsSupplier 
  
 oktaSupplier 
  
 = 
  
 new 
  
 OktaClientCredentialsSupplier 
 ( 
 oktaDomain 
 , 
  
 oktaClientId 
 , 
  
 oktaClientSecret 
 ); 
  
  IdentityPoolCredentials 
 
 . 
 Builder 
  
 credentialsBuilder 
  
 = 
  
  IdentityPoolCredentials 
 
 . 
 newBuilder 
 () 
  
 . 
 setAudience 
 ( 
 gcpWorkloadAudience 
 ) 
  
 // This token type indicates that the subject token is a JSON Web Token (JWT). 
  
 // This is required for Workload Identity Federation with an OIDC provider like Okta. 
  
 . 
 setSubjectTokenType 
 ( 
 "urn:ietf:params:oauth:token-type:jwt" 
 ) 
  
 . 
 setTokenUrl 
 ( 
 "https://sts.googleapis.com/v1/token" 
 ) 
  
 . 
  setSubjectTokenSupplier 
 
 ( 
 oktaSupplier 
 ); 
  
 if 
  
 ( 
 saImpersonationUrl 
  
 != 
  
 null 
 ) 
  
 { 
  
 credentialsBuilder 
 . 
 setServiceAccountImpersonationUrl 
 ( 
 saImpersonationUrl 
 ); 
  
 } 
  
  GoogleCredentials 
 
  
 credentials 
  
 = 
  
 credentialsBuilder 
 . 
 build 
 (); 
  
  Storage 
 
  
 storage 
  
 = 
  
  StorageOptions 
 
 . 
 newBuilder 
 (). 
 setCredentials 
 ( 
 credentials 
 ). 
 build 
 (). 
  getService 
 
 (); 
  
 return 
  
 storage 
 . 
  get 
 
 ( 
 gcsBucketName 
 ); 
  
 } 
  
 /** 
 * A custom SubjectTokenSupplier that authenticates with Okta using the Client Credentials grant 
 * flow. 
 */ 
  
 private 
  
 static 
  
 class 
 OktaClientCredentialsSupplier 
  
 implements 
  
  IdentityPoolSubjectTokenSupplier 
 
  
 { 
  
 private 
  
 static 
  
 final 
  
 long 
  
 TOKEN_REFRESH_BUFFER_SECONDS 
  
 = 
  
 60 
 ; 
  
 private 
  
 final 
  
 String 
  
 oktaTokenUrl 
 ; 
  
 private 
  
 final 
  
 String 
  
 clientId 
 ; 
  
 private 
  
 final 
  
 String 
  
 clientSecret 
 ; 
  
 private 
  
 String 
  
 accessToken 
 ; 
  
 private 
  
 Instant 
  
 expiryTime 
 ; 
  
 public 
  
 OktaClientCredentialsSupplier 
 ( 
 String 
  
 domain 
 , 
  
 String 
  
 clientId 
 , 
  
 String 
  
 clientSecret 
 ) 
  
 { 
  
 // Ensure domain doesn't have a trailing slash for cleaner URL construction 
  
 String 
  
 cleanedDomain 
  
 = 
  
 domain 
 . 
  endsWith 
 
 ( 
 "/" 
 ) 
  
 ? 
  
 domain 
 . 
 substring 
 ( 
 0 
 , 
  
 domain 
 . 
 length 
 () 
  
 - 
  
 1 
 ) 
  
 : 
  
 domain 
 ; 
  
 this 
 . 
 oktaTokenUrl 
  
 = 
  
 cleanedDomain 
  
 + 
  
 "/oauth2/default/v1/token" 
 ; 
  
 this 
 . 
 clientId 
  
 = 
  
 clientId 
 ; 
  
 this 
 . 
 clientSecret 
  
 = 
  
 clientSecret 
 ; 
  
 } 
  
 /** 
 * Main method called by the auth library. It will fetch a new token if one is not already 
 * cached. 
 */ 
  
 @Override 
  
 public 
  
 String 
  
 getSubjectToken 
 ( 
  ExternalAccountSupplierContext 
 
  
 context 
 ) 
  
 throws 
  
 IOException 
  
 { 
  
 // Check if the current token is still valid (with a 60-second buffer). 
  
 boolean 
  
 isTokenValid 
  
 = 
  
 this 
 . 
 accessToken 
  
 != 
  
 null 
 && 
 this 
 . 
 expiryTime 
  
 != 
  
 null 
 && 
 Instant 
 . 
 now 
 (). 
 isBefore 
 ( 
 this 
 . 
 expiryTime 
 . 
 minusSeconds 
 ( 
 TOKEN_REFRESH_BUFFER_SECONDS 
 )); 
  
 if 
  
 ( 
 isTokenValid 
 ) 
  
 { 
  
 return 
  
 this 
 . 
 accessToken 
 ; 
  
 } 
  
 fetchOktaAccessToken 
 (); 
  
 return 
  
 this 
 . 
 accessToken 
 ; 
  
 } 
  
 /** 
 * Performs the Client Credentials grant flow by making a POST request to Okta's token endpoint. 
 */ 
  
 private 
  
 void 
  
 fetchOktaAccessToken 
 () 
  
 throws 
  
 IOException 
  
 { 
  
 URL 
  
 url 
  
 = 
  
 new 
  
 URL 
 ( 
 this 
 . 
 oktaTokenUrl 
 ); 
  
 HttpURLConnection 
  
 conn 
  
 = 
  
 ( 
 HttpURLConnection 
 ) 
  
 url 
 . 
 openConnection 
 (); 
  
 conn 
 . 
 setRequestMethod 
 ( 
 "POST" 
 ); 
  
 conn 
 . 
 setRequestProperty 
 ( 
 "Content-Type" 
 , 
  
 "application/x-www-form-urlencoded" 
 ); 
  
 conn 
 . 
 setRequestProperty 
 ( 
 "Accept" 
 , 
  
 "application/json" 
 ); 
  
 // The client_id and client_secret are sent in a Basic Auth header. 
  
 String 
  
 auth 
  
 = 
  
 this 
 . 
 clientId 
  
 + 
  
 ":" 
  
 + 
  
 this 
 . 
 clientSecret 
 ; 
  
 String 
  
 encodedAuth 
  
 = 
  
 Base64 
 . 
 getEncoder 
 (). 
 encodeToString 
 ( 
 auth 
 . 
 getBytes 
 ( 
 StandardCharsets 
 . 
 UTF_8 
 )); 
  
 conn 
 . 
 setRequestProperty 
 ( 
 "Authorization" 
 , 
  
 "Basic " 
  
 + 
  
 encodedAuth 
 ); 
  
 conn 
 . 
 setDoOutput 
 ( 
 true 
 ); 
  
 try 
  
 ( 
 java 
 . 
 io 
 . 
 OutputStream 
  
 out 
  
 = 
  
 conn 
 . 
 getOutputStream 
 ()) 
  
 { 
  
 // Scopes define the permissions the access token will have. 
  
 // Update "gcp.test.read" to match your Okta configuration. 
  
 String 
  
 params 
  
 = 
  
 "grant_type=client_credentials&scope=gcp.test.read" 
 ; 
  
 out 
 . 
 write 
 ( 
 params 
 . 
 getBytes 
 ( 
 StandardCharsets 
 . 
 UTF_8 
 )); 
  
 out 
 . 
 flush 
 (); 
  
 } 
  
 int 
  
 responseCode 
  
 = 
  
 conn 
 . 
 getResponseCode 
 (); 
  
 if 
  
 ( 
 responseCode 
  
 == 
  
 HttpURLConnection 
 . 
 HTTP_OK 
 ) 
  
 { 
  
 try 
  
 ( 
 BufferedReader 
  
 in 
  
 = 
  
 new 
  
 BufferedReader 
 ( 
  
 new 
  
 InputStreamReader 
 ( 
 conn 
 . 
 getInputStream 
 (), 
  
 StandardCharsets 
 . 
 UTF_8 
 ))) 
  
 { 
  
  GenericJson 
 
  
 jsonObject 
  
 = 
  
  GsonFactory 
 
 . 
 getDefaultInstance 
 (). 
 createJsonParser 
 ( 
 in 
 ). 
 parse 
 ( 
  GenericJson 
 
 . 
 class 
 ); 
  
 if 
  
 ( 
 jsonObject 
 . 
 containsKey 
 ( 
 "access_token" 
 ) 
 && 
 jsonObject 
 . 
 containsKey 
 ( 
 "expires_in" 
 )) 
  
 { 
  
 this 
 . 
 accessToken 
  
 = 
  
 ( 
 String 
 ) 
  
 jsonObject 
 . 
 get 
 ( 
 "access_token" 
 ); 
  
 Number 
  
 expiresInNumber 
  
 = 
  
 ( 
 Number 
 ) 
  
 jsonObject 
 . 
 get 
 ( 
 "expires_in" 
 ); 
  
 this 
 . 
 expiryTime 
  
 = 
  
 Instant 
 . 
 now 
 (). 
 plusSeconds 
 ( 
 expiresInNumber 
 . 
 longValue 
 ()); 
  
 } 
  
 else 
  
 { 
  
 throw 
  
 new 
  
 IOException 
 ( 
 "Access token or expires_in not found in Okta response." 
 ); 
  
 } 
  
 } 
  
 } 
  
 else 
  
 { 
  
 throw 
  
 new 
  
 IOException 
 ( 
 "Failed to authenticate with Okta. Response code: " 
  
 + 
  
 responseCode 
 ); 
  
 } 
  
 } 
  
 } 
 

What's next

Design a Mobile Site
View Site in Mobile | Classic
Share by: