secrets = gson.fromJson(reader, type);
+
+ if (secrets == null) {
+ return;
+ }
+
+ // AWS SDK for Java looks for System Properties with specific names (camelCase)
+ // if environment variables are missing.
+ if (secrets.containsKey("aws_access_key_id")) {
+ System.setProperty("aws.accessKeyId", secrets.get("aws_access_key_id"));
+ }
+ if (secrets.containsKey("aws_secret_access_key")) {
+ System.setProperty("aws.secretAccessKey", secrets.get("aws_secret_access_key"));
+ }
+ if (secrets.containsKey("aws_region")) {
+ System.setProperty("aws.region", secrets.get("aws_region"));
+ }
+
+ // Set custom GCP variables as System Properties so getConfiguration() can find them.
+ if (secrets.containsKey("gcp_workload_audience")) {
+ System.setProperty("GCP_WORKLOAD_AUDIENCE", secrets.get("gcp_workload_audience"));
+ }
+ if (secrets.containsKey("gcs_bucket_name")) {
+ System.setProperty("GCS_BUCKET_NAME", secrets.get("gcs_bucket_name"));
+ }
+ if (secrets.containsKey("gcp_service_account_impersonation_url")) {
+ System.setProperty(
+ "GCP_SERVICE_ACCOUNT_IMPERSONATION_URL",
+ secrets.get("gcp_service_account_impersonation_url"));
+ }
+
+ } catch (IOException e) {
+ System.err.println("Error reading secrets file: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Authenticates using a custom AWS credential supplier and retrieves bucket metadata.
+ *
+ * @param gcpWorkloadAudience The WIF provider audience.
+ * @param saImpersonationUrl Optional service account impersonation URL.
+ * @param gcsBucketName The GCS bucket name.
+ * @return The Bucket object containing metadata.
+ * @throws IOException If authentication fails.
+ */
+ // [START auth_custom_credential_supplier_aws]
+ 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.
+ *
+ * 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);
+ }
+ }
+ // [END auth_custom_credential_supplier_aws]
+}
diff --git a/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/Dockerfile b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/Dockerfile
new file mode 100644
index 00000000000..49069fc6410
--- /dev/null
+++ b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/Dockerfile
@@ -0,0 +1,33 @@
+# Build the application
+FROM maven:3.9-eclipse-temurin-17 AS builder
+
+WORKDIR /app
+
+# Copy only the build config first (for better layer caching)
+COPY pom.xml .
+COPY src ./src
+
+# 'clean package': Compiles the code and creates the thin jar in /app/target
+# 'dependency:copy-dependencies': Copies all JARs to /app/target/libs
+# We explicitly set -DoutputDirectory so we know EXACTLY where they are.
+RUN mvn clean package dependency:copy-dependencies \
+ -DoutputDirectory=target/libs \
+ -DskipTests
+
+# Run the application
+FROM eclipse-temurin:17-jre-focal
+
+# Security: Create a non-root user
+RUN useradd -m appuser
+USER appuser
+WORKDIR /app
+
+# Copy the Thin Jar
+COPY --from=builder --chown=appuser:appuser /app/target/auth-1.0.jar app.jar
+
+# Copy the Dependencies (The libraries)
+COPY --from=builder --chown=appuser:appuser /app/target/libs lib/
+
+# Run with Classpath
+# We add 'app.jar' and everything in 'lib/' to the classpath.
+CMD ["java", "-cp", "app.jar:lib/*", "com.google.cloud.auth.samples.customcredentials.aws.CustomCredentialSupplierAwsWorkload"]
diff --git a/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/README.md b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/README.md
new file mode 100644
index 00000000000..6dd7d5428c8
--- /dev/null
+++ b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/README.md
@@ -0,0 +1,124 @@
+# Running the Custom AWS Credential Supplier Sample (Java)
+
+This sample demonstrates how to use a custom AWS security credential supplier to authenticate with Google Cloud using AWS as an external identity provider. It uses the **AWS SDK for Java (v2)** to fetch credentials from sources like Amazon Elastic Kubernetes Service (EKS) with IAM Roles for Service Accounts (IRSA), Elastic Container Service (ECS), or Fargate.
+
+## Prerequisites
+
+* An AWS account.
+* A Google Cloud project with the IAM API enabled.
+* A GCS bucket.
+* **Java 11** or later installed.
+* **Maven** installed.
+
+If you want to use AWS security credentials that cannot be retrieved using methods supported natively by the Google Auth library, a custom `AwsSecurityCredentialsSupplier` implementation may be specified. The supplier must return valid, unexpired AWS security credentials when called by the Google Cloud Auth library.
+
+## Running Locally
+
+For local development, you can provide credentials and configuration in a JSON file.
+
+### Build the Project
+
+Ensure you have Java and Maven installed, then build the project to download dependencies and create an executable JAR:
+
+```bash
+mvn clean package
+```
+
+### Configure Credentials for Local Development
+
+1. Copy the example secrets file to a new file named `custom-credentials-aws-secrets.json` in the project root:
+ ```bash
+ cp custom-credentials-aws-secrets.json.example custom-credentials-aws-secrets.json
+ ```
+2. Open `custom-credentials-aws-secrets.json` and fill in the required values for your AWS and Google Cloud configuration. Do not check your `custom-credentials-aws-secrets.json` file into version control.
+
+**Note:** This file is only used for local development and is not needed when running in a containerized environment like EKS with IRSA.
+
+### Run the Application
+
+Execute the JAR file generated in the `target` directory:
+
+```bash
+java -jar target/custom-credential-aws-1.0-SNAPSHOT.jar
+```
+
+*Note: Adjust the JAR filename version if you modified it in your `pom.xml`.*
+
+When run locally, the application will detect the `custom-credentials-aws-secrets.json` file and use it to configure the necessary system properties for the AWS SDK.
+
+## Running in a Containerized Environment (EKS)
+
+This section provides a brief overview of how to run the sample in an Amazon EKS cluster.
+
+### EKS Cluster Setup
+
+First, you need an EKS cluster. You can create one using `eksctl` or the AWS Management Console. For detailed instructions, refer to the [Amazon EKS documentation](https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html).
+
+### Configure IAM Roles for Service Accounts (IRSA)
+
+IRSA enables you to associate an IAM role with a Kubernetes service account. This provides a secure way for your pods to access AWS services without hardcoding long-lived credentials.
+
+Run the following command to create the IAM role and bind it to a Kubernetes Service Account:
+
+```bash
+eksctl create iamserviceaccount \
+ --name your-k8s-service-account \
+ --namespace default \
+ --cluster your-cluster-name \
+ --region your-aws-region \
+ --role-name your-role-name \
+ --attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
+ --approve
+```
+
+> **Note**: The `--attach-policy-arn` flag is used here to demonstrate attaching permissions. Update this with the specific AWS policy ARN your application requires.
+
+For a deep dive into how this works without using `eksctl`, refer to the [IAM Roles for Service Accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) documentation.
+
+### Configure Google Cloud to Trust the AWS Role
+
+To allow your AWS role to authenticate as a Google Cloud service account, you need to configure Workload Identity Federation. This process involves these key steps:
+
+1. **Create a Workload Identity Pool and an AWS Provider:** The pool holds the configuration, and the provider is set up to trust your AWS account.
+
+2. **Create or select a Google Cloud Service Account:** This service account will be impersonated by your AWS role.
+
+3. **Bind the AWS Role to the Google Cloud Service Account:** Create an IAM policy binding that gives your AWS role the `Workload Identity User` (`roles/iam.workloadIdentityUser`) role on the Google Cloud service account.
+
+For more detailed information, see the documentation on [Configuring Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-clouds).
+
+### Containerize and Package the Application
+
+Create a `Dockerfile` for the Java application and push the image to a container registry (for example Amazon ECR) that your EKS cluster can access.
+
+**Note:** The provided [`Dockerfile`](Dockerfile) is an example that may need modification for your specific needs.
+
+Build and push the image:
+```bash
+docker build -t your-container-image:latest .
+docker push your-container-image:latest
+```
+
+### Deploy to EKS
+
+Create a Kubernetes deployment manifest to deploy your application to the EKS cluster. See the [`pod.yaml`](pod.yaml) file for an example.
+
+**Note:** The provided [`pod.yaml`](pod.yaml) is an example and may need to be modified for your specific needs.
+
+Deploy the pod:
+
+```bash
+kubectl apply -f pod.yaml
+```
+
+### Clean Up
+
+To clean up the resources, delete the EKS cluster and any other AWS and Google Cloud resources you created.
+
+```bash
+eksctl delete cluster --name your-cluster-name
+```
+
+## Testing
+
+This sample is not continuously tested. It is provided for instructional purposes and may require modifications to work in your environment.
diff --git a/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/custom-credentials-aws-secrets.json.example b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/custom-credentials-aws-secrets.json.example
new file mode 100644
index 00000000000..300dc70c138
--- /dev/null
+++ b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/custom-credentials-aws-secrets.json.example
@@ -0,0 +1,8 @@
+{
+ "aws_access_key_id": "YOUR_AWS_ACCESS_KEY_ID",
+ "aws_secret_access_key": "YOUR_AWS_SECRET_ACCESS_KEY",
+ "aws_region": "YOUR_AWS_REGION",
+ "gcp_workload_audience": "YOUR_GCP_WORKLOAD_AUDIENCE",
+ "gcs_bucket_name": "YOUR_GCS_BUCKET_NAME",
+ "gcp_service_account_impersonation_url": "YOUR_GCP_SERVICE_ACCOUNT_IMPERSONATION_URL"
+}
diff --git a/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/pod.yaml b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/pod.yaml
new file mode 100644
index 00000000000..7cc35c293ca
--- /dev/null
+++ b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/pod.yaml
@@ -0,0 +1,44 @@
+# Copyright 2025 Google LLC
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apiVersion: v1
+kind: Pod
+metadata:
+ name: custom-credential-pod-java
+spec:
+ # The Kubernetes Service Account that is annotated with the corresponding
+ # AWS IAM role ARN. See the README for instructions on setting up IAM
+ # Roles for Service Accounts (IRSA).
+ serviceAccountName: your-k8s-service-account
+ containers:
+ - name: gcp-auth-sample-java
+ # The container image pushed to the container registry
+ # For example, Amazon Elastic Container Registry
+ image: your-container-image:latest
+ env:
+ # REQUIRED: The AWS region. The AWS SDK for Java requires this
+ # to be set explicitly in containers.
+ - name: AWS_REGION
+ value: "your-aws-region"
+
+ # REQUIRED: The full identifier of the Workload Identity Pool provider
+ - name: GCP_WORKLOAD_AUDIENCE
+ value: "your-gcp-workload-audience"
+
+ # OPTIONAL: Enable Google Cloud service account impersonation
+ # - name: GCP_SERVICE_ACCOUNT_IMPERSONATION_URL
+ # value: "your-gcp-service-account-impersonation-url"
+
+ # REQUIRED: The bucket to list
+ - name: GCS_BUCKET_NAME
+ value: "your-gcs-bucket-name"
diff --git a/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/okta/CustomCredentialSupplierOktaWorkload.java b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/okta/CustomCredentialSupplierOktaWorkload.java
new file mode 100644
index 00000000000..9bcbd42bef2
--- /dev/null
+++ b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/okta/CustomCredentialSupplierOktaWorkload.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.auth.samples.customcredentials.okta;
+
+// [START auth_custom_credential_supplier_okta]
+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;
+
+// [END auth_custom_credential_supplier_okta]
+
+/**
+ * This sample demonstrates how to use a custom subject token supplier to authenticate to Google
+ * Cloud Storage, using Okta as the identity provider.
+ */
+public class CustomCredentialSupplierOktaWorkload {
+
+ public static void main(String[] args) throws IOException {
+
+ // Reads the custom-credentials-okta-secrets.json if running locally.
+ loadConfigFromFile();
+
+ // The audience for the workload identity federation.
+ // Format: //iam.googleapis.com/projects//locations/global/
+ // workloadIdentityPools//providers/
+ String gcpWorkloadAudience = getConfiguration("GCP_WORKLOAD_AUDIENCE");
+
+ // The bucket to fetch data from.
+ String gcsBucketName = getConfiguration("GCS_BUCKET_NAME");
+
+ // (Optional) The service account impersonation URL.
+ String saImpersonationUrl = getConfiguration("GCP_SERVICE_ACCOUNT_IMPERSONATION_URL");
+
+ // Okta Configuration
+ String oktaDomain = getConfiguration("OKTA_DOMAIN");
+ String oktaClientId = getConfiguration("OKTA_CLIENT_ID");
+ String oktaClientSecret = getConfiguration("OKTA_CLIENT_SECRET");
+
+ if (gcpWorkloadAudience == null
+ || gcsBucketName == null
+ || oktaDomain == null
+ || oktaClientId == null
+ || oktaClientSecret == null) {
+ System.err.println(
+ "Error: Missing required configuration. "
+ + "Please provide it in a custom-credentials-okta-secrets.json file or as "
+ + "environment variables: GCP_WORKLOAD_AUDIENCE, GCS_BUCKET_NAME, "
+ + "OKTA_DOMAIN, OKTA_CLIENT_ID, OKTA_CLIENT_SECRET");
+ return;
+ }
+
+ try {
+ System.out.println("Getting metadata for bucket: " + gcsBucketName + "...");
+ Bucket bucket =
+ authenticateWithOktaCredentials(
+ gcpWorkloadAudience,
+ saImpersonationUrl,
+ gcsBucketName,
+ oktaDomain,
+ oktaClientId,
+ oktaClientSecret);
+
+ System.out.println(" --- SUCCESS! ---");
+ System.out.printf("Bucket Name: %s%n", bucket.getName());
+ System.out.printf("Bucket Location: %s%n", bucket.getLocation());
+ } catch (Exception e) {
+ System.err.println("Authentication or Request failed: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Helper method to retrieve configuration. It checks Environment variables first, then System
+ * properties (populated by loadConfigFromFile).
+ */
+ static String getConfiguration(String key) {
+ String value = System.getenv(key);
+ if (value == null) {
+ value = System.getProperty(key);
+ }
+ return value;
+ }
+
+ /**
+ * If a local secrets file is present, load it into the System Properties. This is a
+ * "just-in-time" configuration for local development. These variables are only set for the
+ * current process.
+ */
+ static void loadConfigFromFile() {
+ // By default, this expects the file to be in the project root.
+ String secretsFilePath = "custom-credentials-okta-secrets.json";
+ if (!Files.exists(Paths.get(secretsFilePath))) {
+ return;
+ }
+
+ try (Reader reader = Files.newBufferedReader(Paths.get(secretsFilePath))) {
+ Gson gson = new Gson();
+ Type type = new TypeToken