GCP Authentication with gcloud CLI

Quote

“Passwords are like underwear: you don’t let people see it, you should change it very often, and you shouldn’t share it with strangers.”

Chris Pirillo

Federation context drift

The local gcloud auth list and token-inspection commands below were refreshed live on April 15, 2026. The bq-wh-nb workload identity pool, provider, and service-account examples remain documented patterns from the original setup and were not re-executed during this pass because the underlying demo project is no longer a healthy live baseline.

Authentication Overview

There are four common authentication flows: interactive login for humans, Application Default Credentials for local code, Workload Identity Federation for external workloads that can present OIDC tokens, and attached service accounts for Google-managed runtimes. Service account key files remain a fallback for environments that cannot use OIDC or a metadata server.


flowchart TD
    A["Choose an authentication path"] --> B["Human on local workstation"]
    A --> C["External workload with OIDC"]
    A --> D["External workload without OIDC"]
    A --> E["Google-managed runtime"]

    B --> F["gcloud auth login<br/>+ gcloud auth application-default login"]
    C --> G["Workload Identity Federation<br/>OIDC token -> STS -> service account impersonation"]
    D --> H["gcloud auth activate-service-account<br/>--key-file=key.json"]
    E --> I["Attached service account<br/>metadata server"]

Authentication Commands

The gcloud auth subcommand manages all credential types. Choose the method that matches your environment: interactive for local work, application-default for SDK access, or service account for production and CI/CD.

Interactive Authentication

For human users working locally. auth login populates credentials for the gcloud CLI; application-default login populates a separate file read by Python, Go, and Java client libraries. Both are needed for full local development access.

gcloud auth login - interactive authentication for human users

Opens a browser for Google account login. The OAuth token is stored in ~/.config/gcloud/ and refreshed automatically. Use for interactive work (debugging, ad-hoc queries, infrastructure changes).

gcloud auth login
Your browser has been opened to visit:
 
    https://accounts.google.com/o/oauth2/auth?...
 
You are now logged in as [you@example.com].
Your current project is [my-project]. You can change this setting by running:
  $ gcloud config set project PROJECT_ID

gcloud auth application-default login - ADC for application code

Writes credentials to ~/.config/gcloud/application_default_credentials.json. These are read by Python google-cloud-*, Go, and Java SDK clients, not by the gcloud CLI itself.

ADC is different from gcloud auth login

  • gcloud auth login creates credentials for the gcloud CLI itself.
  • gcloud auth application-default login creates credentials for client libraries such as Python google-cloud-*, Go, and Java SDKs.
  • If your pipeline code uses a client library, the CLI login alone does not satisfy ADC.

Run both commands for local development

For local development, run both commands when you need CLI access and SDK access in the same environment.

gcloud auth login
gcloud auth application-default login
gcloud auth application-default login
Your browser has been opened to visit:
 
    https://accounts.google.com/o/oauth2/auth?...
 
Credentials saved to file: [/home/user/.config/gcloud/application_default_credentials.json]
 
These credentials will be used by any library that requests Application Default Credentials (ADC).
Quota project "my-project" was added to ADC which can be used by Google client libraries for billing and quota.

gcloud auth application-default set-quota-project - set billing project for ADC

Sets the quota project used by client libraries for billing. Required when your user identity belongs to a different project than the API resources you are accessing, otherwise API calls may fail with quota or billing errors.

gcloud auth application-default set-quota-project PROJECT_ID
Updated property [core/project].
Credentials saved to file: [/home/user/.config/gcloud/application_default_credentials.json]
FlagDescription
PROJECT_IDProject to use for quota and billing when ADC credentials are used by client libraries

Interactive Authentication - Flag Reference

FlagCommandDescription
--no-launch-browserauth login, application-default loginPrint the auth URL instead of opening a browser
--scopesauth login, application-default loginComma-separated OAuth scopes to request
--accountauth loginGoogle account to authenticate if multiple are available
--client-id-fileapplication-default loginPath to a custom OAuth client credentials JSON file
--disable-quota-projectapplication-default loginOmit the billing project from the ADC credentials file

Service Account Authentication

For CI/CD pipelines, automated scripts, and non-interactive environments. In production, prefer Workload Identity Federation or an attached service account over key files, because key files remain valid until explicitly deleted in IAM.

gcloud auth activate-service-account - SA key for production and CI/CD

Authenticates as a service account using a JSON key file. Use for CI/CD pipelines, automated scripts, and non-interactive environments only when you cannot use WIF or a metadata-backed identity.

gcloud auth activate-service-account --key-file=key.json
Activated service account credentials for: [my-sa@my-project.iam.gserviceaccount.com]

For creating and managing the service accounts referenced here, see service-accounts-and-iam. In GitHub Actions, Workload Identity Federation eliminates key files entirely for CI/CD authentication.

Service Account Authentication - Flag Reference

FlagDescription
--key-filePath to the service account JSON key file (required)
--projectSet the default GCP project for this service account session

Credential Management

Commands for inspecting, debugging, and cleaning up credentials. Use gcloud auth list to verify the active identity before running infrastructure commands on a shared or multi-project machine.

gcloud auth list - view authenticated accounts

Lists all authenticated accounts and marks the active one with *. Run before any gcloud command on a shared or multi-project machine to confirm you are using the expected identity.

gcloud auth list
                         Credentialed Accounts
ACTIVE  ACCOUNT
*       alexper.recovery@gmail.com
        bq-wh-sa@bq-wh-nb.iam.gserviceaccount.com
 
To set the active account, run:
    $ gcloud config set account `ACCOUNT`

gcloud auth print-access-token - retrieve the current OAuth token

Prints the raw OAuth 2.0 access token for the active account. Use when debugging direct REST API calls with curl or validating that a credential is active.

gcloud auth print-access-token
ya29.a0Aa7MY...<redacted>

The token was captured live and then redacted in the note because bearer tokens are immediately reusable secrets.

Pass the token directly to REST API calls:

curl -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  "https://bigquery.googleapis.com/bigquery/v2/projects/my-project/datasets"

gcloud auth revoke - remove stored credentials

Revokes and deletes stored credentials for an account. Run when leaving a shared machine, rotating credentials after a security incident, or cleaning up stale accounts.

gcloud auth revoke
Revoked credentials:
 - you@example.com

Credential Management - Flag Reference

FlagCommandDescription
--accountlist, print-access-token, revokeAccount to operate on (default: currently active account)
--allrevokeRevoke all authenticated accounts, not just the active one
--filterlistFilter expression (for example --filter="account:@example.com")
--formatlistOutput format such as json, yaml, table, or value

ADC Credential Search Order

When application code calls a GCP client library, the library calls google.auth.default() to resolve credentials. It searches the following locations in order and stops at the first match.

The ADC search order

When your code does google.auth.default() (covered in 17_py_gcp), ADC checks these locations in this exact order:

  1. GOOGLE_APPLICATION_CREDENTIALS
  2. The local ADC file created by gcloud auth application-default login
  3. The attached service account returned by the metadata server

The first location can point to three different JSON formats:

  • A Workload Identity Federation external_account configuration file
  • A Workforce Identity Federation external_account configuration file
  • A service account key file

Prefer federation or metadata-backed credentials

Use GOOGLE_APPLICATION_CREDENTIALS for a WIF credential configuration file when code runs outside Google Cloud. On Google-managed runtimes, rely on the metadata server. Keep service account key files as a last resort only.

Service account key files never expire on their own

A leaked service account key in a Git repository, container image, or log file remains valid until it is explicitly deleted in IAM.

Prefer keyless authentication

On Compute Engine, Cloud Run, and GKE, use the metadata server. For CI/CD and other external workloads, use WIF so the file on disk is an external_account configuration file instead of a private key.


flowchart TD
    A["google.auth.default()"] --> B{"GOOGLE_APPLICATION_CREDENTIALS<br/>env var set?"}
    B -- "set" --> C{"Credential file type"}
    C --> D["WIF or Workforce<br/>external_account config"]
    C --> E["Service account key<br/>discouraged"]
    B -- "not set" --> F{"Local ADC file exists?"}
    F -- "exists" --> G["application_default_credentials.json"]
    F -- "missing" --> H{"Running on Compute Engine,<br/>Cloud Run, or GKE metadata?"}
    H -- "yes" --> I["Attached service account<br/>metadata-backed token"]
    H -- "no" --> J["AuthenticationError<br/>No credentials found"]
    D --> K["STS exchange and optional<br/>service account impersonation"]
    E --> L["Direct service account credential"]
    G --> M["Local user or impersonated<br/>ADC credential"]
    K --> N["Authenticated"]
    L --> N
    M --> N
    I --> N

Service Account Impersonation

Service account impersonation lets a human user or another service account act as a target service account without downloading its key file. The --impersonate-service-account flag works on any gcloud command and requires roles/iam.serviceAccountTokenCreator on the target service account.

gcloud storage ls gs://my-bucket \
  --impersonate-service-account=my-sa@my-project.iam.gserviceaccount.com
gs://my-bucket/data/
gs://my-bucket/logs/

Use impersonation instead of downloading key files for local testing

To test what a service account can access, impersonate it from your own authenticated session. This avoids creating a persistent key file and the associated security risk. The impersonation token is short-lived and tied to your identity’s audit trail.

FlagDescription
--impersonate-service-accountService account email to impersonate; valid on any gcloud command

Workload Identity Federation

Workload Identity Federation replaces long-lived key files for external workloads. A GitHub Actions job, an on-premises process, or a workload running in another cloud can obtain an external identity token from its IdP, exchange that token with Google Security Token Service (STS), and then impersonate a Google service account to get a short-lived access token.


sequenceDiagram
    participant IdP as External IdP<br/>GitHub Actions
    participant Workload as External workload
    participant STS as Google STS
    participant IAMC as IAM Credentials API
    participant API as Google Cloud API

    IdP->>Workload: OIDC identity token
    Workload->>STS: Exchange subject token
    STS-->>Workload: Federated token
    Workload->>IAMC: Impersonate service account
    IAMC-->>Workload: Short-lived access token
    Workload->>API: Authorized API call

WIF Architecture

WIF has two separate control points. The provider decides whether an external token is trusted at all. The service account IAM binding decides whether that trusted external principal may impersonate a specific Google service account. You need both layers for a secure configuration.

Map the trust chain

The current bq-wh-nb implementation uses GitHub Actions as the external OIDC issuer. The provider maps GitHub claims into Google attributes, the provider condition narrows admission to the alp78 repository owner, and the service account IAM policy narrows impersonation further to one repository.

ComponentCurrent value in bq-wh-nbPurpose
Project number348557092514Appears in provider audiences, principalSet members, and audit references.
Workload identity poolgithub-actionsGroups the external identities trusted by this project.
ProvidergithubValidates GitHub’s OIDC issuer and extracts claims.
Issuer URIhttps://token.actions.githubusercontent.comThe upstream token issuer whose signatures and claims Google validates.
google.subject mappingassertion.subProduces the unique external subject used in audit logs and subject-based principal URIs.
Custom mappingsattribute.repository, attribute.actor, attribute.repository_ownerExpose GitHub claims to IAM policies and provider conditions.
Provider conditionassertion.repository_owner == 'alp78'Rejects tokens whose repository owner is outside the allowed trust boundary.
Target service accountgithub-actions-sa@bq-wh-nb.iam.gserviceaccount.comHolds the Google roles that the external workload ultimately uses.

Shared issuers need an admission guard

GitHub’s OIDC issuer is shared by every repository on GitHub. If a provider trusts the issuer but does not apply an attribute condition, any token that satisfies the provider audience can attempt federation.

Restrict at the provider before IAM

Keep an attribute condition on the provider, such as assertion.repository_owner == 'alp78' or an even narrower repository or branch expression. Then use IAM bindings on the service account to narrow impersonation further.

Inspect the Existing WIF Pool

The pool is the top-level trust boundary. Listing and describing it answers three questions immediately: does the project already have a WIF pool, what is its lifecycle state, and what human-readable purpose did the operator record for it.

List workload identity pools

When auditing an existing project before creating another pool or troubleshooting a failed federation setup. It is typically triggered by first WIF inventory of a project, or confirmation after enabling iam.googleapis.com. Read-only IAM control-plane command. Requires permission to list workload identity pools in the project. Return every pool in bq-wh-nb with its canonical resource name, display name, state, and description.

Output columnSource fieldTypeMeaning
NAMEnamestringFull resource name of the pool, including the project number and location.
DISPLAY_NAMEdisplayNamestringOperator-friendly label shown in the console and CLI.
STATEstateenumLifecycle state of the pool, such as ACTIVE or a deleted state returned with --show-deleted.
DESCRIPTIONdescriptionstringFree-text explanation of what the pool is for.

List every workload identity pool in bq-wh-nb and render the canonical resource name plus lifecycle metadata.

gcloud iam workload-identity-pools list \
  --project=bq-wh-nb \
  --location=global \
  --format="table(name,displayName,state,description)"
NAME                                                                         DISPLAY_NAME    STATE   DESCRIPTION
projects/348557092514/locations/global/workloadIdentityPools/github-actions  GitHub Actions  ACTIVE  WIF pool for GitHub Actions OIDC

The live project currently has one pool, github-actions, and it is active. That tells you the trust boundary already exists and new providers or bindings should normally be attached to this pool instead of creating a duplicate without a reason.

Describe the github-actions pool

After discovering the pool ID and before you bind workloads or create additional providers. It is typically triggered by you need the exact canonical resource name, description, or lifecycle state of one known pool. Read-only IAM control-plane command scoped to one pool resource. Return the authoritative metadata for the github-actions pool.

Output fieldTypeMeaning
namestringCanonical pool resource name used by IAM and API clients.
displayNamestringHuman-readable pool label.
stateenumCurrent lifecycle state of the pool.
descriptionstringOperator-supplied explanation of the pool’s purpose.

Describe the existing github-actions pool and return its canonical metadata in YAML form.

gcloud iam workload-identity-pools describe github-actions \
  --project=bq-wh-nb \
  --location=global \
  --format="yaml(name,displayName,state,description)"
description: WIF pool for GitHub Actions OIDC
displayName: GitHub Actions
name: projects/348557092514/locations/global/workloadIdentityPools/github-actions
state: ACTIVE

The describe output confirms that the pool is active and owned by project number 348557092514. That project number becomes part of both the provider audience and every principalSet://iam.googleapis.com/... member string later in the flow.

FlagCommandSyntaxDescription
WORKLOAD_IDENTITY_POOLdescribegcloud iam workload-identity-pools describe github-actionsPool ID or fully qualified pool resource to inspect.
--locationlist, describe--location=globalLocation of the pool collection or resource. WIF pools use global.
--projectlist, describe--project=bq-wh-nbProject that owns the pool resource.
--formatlist, describe--format="table(...)" or --format="yaml(...)"Controls how the CLI renders the returned fields.
--show-deletedlist--show-deletedIncludes soft-deleted pools in the list output.
--filterlist--filter="state=ACTIVE"Filters listed pools using a gcloud filter expression.
--limitlist--limit=10Caps the number of returned rows.
--page-sizelist--page-size=50Controls server-side page size for long lists.
--sort-bylist--sort-by=displayNameSorts list output by one or more fields before the final limit is applied.

Inspect the Existing WIF Provider

The provider is where Google validates the upstream issuer and translates external claims into Google IAM attributes. This is the object that decides whether a GitHub-issued token is even eligible to reach the service-account impersonation step.

List providers in the github-actions pool

After confirming the pool exists and before reusing or editing a provider. It is typically triggered by you need to see which issuers are already trusted inside the pool and whether they are active. Read-only IAM control-plane command against one pool. Return each provider in the github-actions pool with its issuer and admission condition.

Output columnSource fieldTypeMeaning
NAMEnamestringFull provider resource name, including pool and provider ID.
DISPLAY_NAMEdisplayNamestringHuman-readable provider label.
STATEstateenumLifecycle state of the provider.
ISSUER_URIoidc.issuerUristringExternal OIDC issuer whose tokens the provider validates.
ATTRIBUTE_CONDITIONattributeConditionstringCEL expression that rejects tokens outside the allowed trust boundary.

List the providers inside the github-actions pool and display the issuer plus the provider-side admission guard.

gcloud iam workload-identity-pools providers list \
  --project=bq-wh-nb \
  --location=global \
  --workload-identity-pool=github-actions \
  --format="table(name,displayName,state,oidc.issuerUri,attributeCondition)"
NAME                                                                                          DISPLAY_NAME  STATE   ISSUER_URI                                   ATTRIBUTE_CONDITION
projects/348557092514/locations/global/workloadIdentityPools/github-actions/providers/github  GitHub        ACTIVE  https://token.actions.githubusercontent.com  assertion.repository_owner == 'alp78'

The live pool currently trusts one provider, github. The provider is active, its issuer is the standard GitHub Actions OIDC endpoint, and the provider condition already narrows admission to repositories owned by alp78.

Describe the github provider

After finding the provider ID and before writing a principalSet binding or generating a credential configuration file. It is typically triggered by you need the exact claim mappings, issuer, and provider condition for one known provider. Read-only IAM control-plane command against one provider resource. Return the effective issuer, attribute mappings, and condition used by the github provider.

Output fieldTypeMeaning
namestringCanonical provider resource name used in audiences and API calls.
displayNamestringHuman-readable provider label.
stateenumCurrent lifecycle state of the provider.
oidc.issuerUristringOIDC issuer URL Google validates against.
attributeMappingmapClaim-to-attribute mappings that feed IAM principal URIs and logs.
attributeConditionstringCEL expression that must evaluate to true for the token to be accepted.

Describe the existing github provider and surface the live issuer, mappings, and provider condition.

gcloud iam workload-identity-pools providers describe github \
  --project=bq-wh-nb \
  --location=global \
  --workload-identity-pool=github-actions \
  --format="yaml(name,displayName,state,oidc.issuerUri,attributeMapping,attributeCondition)"
attributeCondition: assertion.repository_owner == 'alp78'
attributeMapping:
  attribute.actor: assertion.actor
  attribute.repository: assertion.repository
  attribute.repository_owner: assertion.repository_owner
  google.subject: assertion.sub
displayName: GitHub
name: projects/348557092514/locations/global/workloadIdentityPools/github-actions/providers/github
oidc:
  issuerUri: https://token.actions.githubusercontent.com
state: ACTIVE

The provider maps GitHub’s sub claim to google.subject, which is the identity Google records in audit logs and exposes through principal URIs. It also maps repository, actor, and repository-owner claims so IAM can grant repository-scoped access instead of trusting the whole pool.

FlagCommandSyntaxDescription
PROVIDERdescribegcloud iam workload-identity-pools providers describe githubProvider ID or fully qualified provider resource to inspect.
--workload-identity-poollist, describe--workload-identity-pool=github-actionsPool that contains the provider resource.
--locationlist, describe--location=globalLocation of the pool and provider resources.
--projectlist, describe--project=bq-wh-nbProject that owns the pool and provider.
--formatlist, describe--format="table(...)" or --format="yaml(...)"Controls the rendered output fields.
--show-deletedlist--show-deletedIncludes soft-deleted providers in the list output.
--filterlist--filter="state=ACTIVE"Filters provider rows with a gcloud expression.
--limitlist--limit=10Caps the number of providers returned.
--page-sizelist--page-size=50Sets the paging size for long provider lists.
--sort-bylist--sort-by=displayNameSorts provider rows before the final limit is applied.

Create a WIF Pool and Provider

Creating WIF is a two-step control-plane operation. First you create the pool that defines the trust boundary. Then you create a provider inside that pool that names one issuer and the exact claim-to-attribute mapping logic. In a project that already has a working provider, use environment-specific or disposable IDs so you do not overwrite an active configuration.

Create a new workload identity pool

When a project needs a new trust boundary for one external platform or environment. It is typically triggered by initial WIF bootstrap, new CI/CD platform onboarding, or separation of dev and prod trust domains. State-changing IAM control-plane command. Requires permission to create workload identity pools in the target project. Create the container resource that will own one or more external identity providers.

Create a new workload identity pool that will later host one or more external OIDC providers.

gcloud iam workload-identity-pools create gha-p4-demo01 \
  --project=bq-wh-nb \
  --location=global \
  --display-name="GitHub Actions Demo" \
  --description="Disposable WIF pool for authentication note live capture"
Created workload identity pool [gha-p4-demo01].

The pool ID is the stable resource name you reference later in provider creation and principalSet URIs. displayName is purely human-readable. description is operational metadata and should explain which external platform or environment the pool exists for.

Create an OIDC provider inside the pool

After the pool exists and you know the issuer URI, claim mapping, and admission criteria for the external platform. It is typically triggered by WIF bootstrap for a new IdP or a new environment-specific trust boundary. State-changing IAM control-plane command. Requires permission to create providers inside the target pool. Define which OIDC issuer is trusted, which claims become Google attributes, and which tokens are rejected before service-account impersonation is considered.

Mapping targetLive mappingWhy it matters
google.subjectassertion.subProduces the unique external subject used in audit logs and subject-based principal URIs.
attribute.repositoryassertion.repositoryEnables repository-scoped principalSet bindings such as alp78/git-lab.
attribute.actorassertion.actorExposes the GitHub actor claim for optional future policy decisions.
attribute.repository_ownerassertion.repository_ownerFeeds the provider admission guard so only the intended GitHub owner is trusted.

Create an OIDC provider that trusts GitHub’s issuer, maps the required claims, and restricts admission to the alp78 repository owner.

gcloud iam workload-identity-pools providers create-oidc github-demo \
  --project=bq-wh-nb \
  --location=global \
  --workload-identity-pool=gha-p4-demo01 \
  --display-name="GitHub OIDC Demo" \
  --description="Disposable GitHub OIDC provider for note capture" \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.actor=assertion.actor,attribute.repository_owner=assertion.repository_owner" \
  --attribute-condition="assertion.repository_owner == 'alp78'"
Created workload identity pool provider [github-demo].

The provider creation command performs the critical security work. --issuer-uri identifies who may sign the external token. --attribute-mapping defines which claims survive into Google IAM. --attribute-condition is the early rejection gate that stops tokens from unrelated repositories before they ever reach service-account impersonation.

Deleting a pool is an outage event

gcloud iam workload-identity-pools delete stops new token exchanges immediately. Google Cloud can still return deleted pools with --show-deleted, and gcloud iam workload-identity-pools undelete exists, but workloads fail until the pool is restored.

Test in a disposable pool first

Use a disposable pool for validation work and keep production pools stable. If you need to pause access temporarily, a disabled pool or narrower provider condition is safer than deleting a production trust boundary.

FlagCommandSyntaxDescription
WORKLOAD_IDENTITY_POOLcreategcloud iam workload-identity-pools create gha-p4-demo01Pool ID to create.
--locationcreate--location=globalLocation of the new pool. WIF pools use global.
--display-namecreate--display-name="GitHub Actions Demo"Human-readable pool label.
--descriptioncreate--description="..."Operational description of the pool’s purpose.
--disabledcreate--disabledCreates the pool in a disabled state so it cannot exchange tokens yet.
--modecreate--mode=federation-onlySets the pool mode when you need a non-default trust-domain behavior.
--inline-trust-config-filecreate--inline-trust-config-file=trust.yamlSupplies additional trust bundles from a YAML file.
--inline-certificate-issuance-config-filecreate--inline-certificate-issuance-config-file=issuance.yamlSupplies certificate issuance settings for certificate-based scenarios.
--use-default-shared-cacreate--use-default-shared-caUses Google-managed shared CAs for certificate issuance.
--projectcreate--project=bq-wh-nbProject that will own the pool resource.
FlagCommandSyntaxDescription
------------
PROVIDERcreate-oidcgcloud iam workload-identity-pools providers create-oidc github-demoProvider ID to create inside the pool.
--workload-identity-poolcreate-oidc--workload-identity-pool=gha-p4-demo01Existing pool that will contain the provider.
--locationcreate-oidc--location=globalLocation of the parent pool and new provider.
--issuer-uricreate-oidc--issuer-uri="https://token.actions.githubusercontent.com"Trusted OIDC issuer URL.
--attribute-mappingcreate-oidc--attribute-mapping="google.subject=assertion.sub,..."Maps claims from the external token into Google IAM attributes.
--attribute-conditioncreate-oidc--attribute-condition="assertion.repository_owner == 'alp78'"CEL expression that rejects tokens outside the allowed trust boundary.
--allowed-audiencescreate-oidc--allowed-audiences=https://exampleRestricts which aud values are accepted from the external token.
--display-namecreate-oidc--display-name="GitHub OIDC Demo"Human-readable provider label.
--descriptioncreate-oidc--description="..."Operational description of the provider.
--disabledcreate-oidc--disabledCreates the provider but leaves token exchange disabled.
--jwk-json-pathcreate-oidc--jwk-json-path=keys.jsonSupplies a local JWKS file when the issuer keys are not discoverable automatically.
--projectcreate-oidc--project=bq-wh-nbProject that owns the pool and provider resources.

Service Account Binding for WIF

Provider trust and service-account authorization are separate. Even if the provider accepts the token, the workload still cannot do anything until the target service account grants roles/iam.workloadIdentityUser to a federated principal or principal set.

Grant repository-scoped impersonation to the service account

After the provider exists and you know which external identities should impersonate the target service account. It is typically triggered by WIF bootstrap for a repository, workload, or environment that now needs Google API access. State-changing IAM policy command against a service account resource. Requires permission to modify the service account IAM policy. Grant the github-actions pool identities for repository alp78/git-lab permission to impersonate github-actions-sa.

Output fieldTypeMeaning
bindings.rolestringIAM role granted on the service account resource.
bindings.membersarrayPrincipals or principal sets that receive the role.
etagstringConcurrency token for optimistic IAM policy updates.
versionintegerIAM policy schema version.

principalSet member anatomy

The member string has four parts:

  • projects/348557092514/locations/global/workloadIdentityPools/github-actions identifies the pool host project and pool.
  • attribute.repository says the binding keys off the mapped repository claim.
  • alp78/git-lab is the repository value that must appear in the mapped attribute.
  • roles/iam.workloadIdentityUser authorizes service-account impersonation, not direct resource access. The service account’s own project roles still determine what APIs it may call.

Grant repository-scoped WIF identities permission to impersonate github-actions-sa and return the resulting IAM policy.

gcloud iam service-accounts add-iam-policy-binding \
  github-actions-sa@bq-wh-nb.iam.gserviceaccount.com \
  --project=bq-wh-nb \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/projects/348557092514/locations/global/workloadIdentityPools/github-actions/attribute.repository/alp78/git-lab"
bindings:
- members:
  - principalSet://iam.googleapis.com/projects/348557092514/locations/global/workloadIdentityPools/github-actions/attribute.repository/alp78/git-lab
  role: roles/iam.workloadIdentityUser
etag: BwZPVI5NoOc=
version: 1
 
Updated IAM policy for serviceAccount [github-actions-sa@bq-wh-nb.iam.gserviceaccount.com].

The binding is repository-scoped rather than pool-scoped. That is the safer pattern. The provider condition limits which GitHub owner may exchange tokens, and the service-account policy limits impersonation to one specific repository under that owner.

principalSet and principal are different scopes

principalSet://.../attribute.repository/alp78/git-lab matches every federated identity whose mapped repository claim equals alp78/git-lab. principal://.../subject/SUBJECT matches one exact google.subject value only.

Pick the narrowest practical scope

Use principalSet when you want repository-level or claim-based grouping. Use principal when one exact subject should impersonate the service account and nothing else.

FlagSyntaxDescription
SERVICE_ACCOUNTgcloud iam service-accounts add-iam-policy-binding github-actions-sa@bq-wh-nb.iam.gserviceaccount.comService account resource whose IAM policy you are modifying.
--member--member="principalSet://iam.googleapis.com/..."Federated principal or principal set to grant access to.
--role--role="roles/iam.workloadIdentityUser"IAM role granted on the service account resource.
--condition--condition='expression=...,title=...'Optional IAM condition attached to the binding itself.
--condition-from-file--condition-from-file=condition.yamlReads the IAM condition from a local JSON or YAML file.
--project--project=bq-wh-nbProject that owns the service account resource.

Credential Configuration File

External workloads do not store a service account private key when using WIF. Instead, they store an external_account configuration file that points to the external token source, the STS token endpoint, and the IAM Credentials impersonation endpoint.

Generate the external_account credential file

After the provider and service-account binding exist and you know how the external workload will expose its OIDC token. It is typically triggered by initial bootstrap of a CI runner, another cloud workload, or an on-premises process that needs keyless Google authentication. Local file-generation command. It does not create an IAM resource; it writes a JSON configuration file for ADC or gcloud. Generate the JSON file that tells auth libraries how to exchange an external OIDC token for Google credentials.

Create a WIF credential configuration file that reads the subject token from a local file and impersonates github-actions-sa.

gcloud iam workload-identity-pools create-cred-config `
  projects/348557092514/locations/global/workloadIdentityPools/github-actions/providers/github `
  --service-account=github-actions-sa@bq-wh-nb.iam.gserviceaccount.com `
  --credential-source-file=.\github-oidc-token.txt `
  --output-file=.\wif-cred-config.json
Created credential configuration file [.\wif-cred-config.json].

The first positional argument is the provider audience, not the project ID. The resulting file does not contain a private key. It contains metadata that tells the auth library where to read the external token and which Google endpoints to call next.

Inspect the generated JSON structure

Immediately after generating the file, or when auditing a credential file supplied to a deployment system. It is typically triggered by you need to verify that the file points to the intended provider, token source, and service account impersonation endpoint. Local file inspection. Read-only against the generated JSON file. Confirm that the file is an external_account configuration and not a service account key.

JSON fieldTypeMeaning
typestringCredential file type. For WIF this is external_account, not service_account.
audiencestringCanonical provider audience used in the STS token exchange.
subject_token_typestringToken type expected from the external IdP.
token_urlstringSTS endpoint used to exchange the external token for a Google federated token.
credential_source.filestringLocal file path where the workload obtains the external OIDC token.
service_account_impersonation_urlstringIAM Credentials endpoint that mints the short-lived Google access token.
universe_domainstringGoogle API domain suffix used by the generated configuration.

Read the generated JSON file and confirm that it contains token-exchange metadata rather than a private key.

{
  "universe_domain": "googleapis.com",
  "type": "external_account",
  "audience": "//iam.googleapis.com/projects/348557092514/locations/global/workloadIdentityPools/github-actions/providers/github",
  "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
  "token_url": "https://sts.googleapis.com/v1/token",
  "credential_source": {
    "file": ".\\github-oidc-token.txt"
  },
  "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/github-actions-sa@bq-wh-nb.iam.gserviceaccount.com:generateAccessToken"
}

Validate credential configuration files from outside your control

A credential configuration file can point your workload at specific URLs and local file paths. If an attacker can swap that JSON, they can redirect authentication traffic or token reads.

Generate or review the JSON yourself

Generate the file with gcloud iam workload-identity-pools create-cred-config when possible, or review every field before distributing it to a runner, VM, or container image.

FlagSyntaxDescription
AUDIENCEprojects/348557092514/locations/global/workloadIdentityPools/github-actions/providers/githubFully qualified provider identifier used by STS as the audience.
--output-file--output-file=.\wif-cred-config.jsonPath where the generated JSON file is written.
--credential-source-file--credential-source-file=.\github-oidc-token.txtLocal file that will contain the external subject token.
--credential-source-url--credential-source-url=https://example/tokenRemote endpoint that returns the external subject token instead of a local file.
--executable-command--executable-command=/abs/path/get-tokenExecutable that returns the external subject token on demand.
--aws--awsBuilds an AWS-based credential configuration.
--azure--azureBuilds an Azure-based credential configuration.
--credential-cert-path--credential-cert-path=cert.pemBuilds an X.509 certificate-based credential configuration.
--service-account--service-account=github-actions-sa@bq-wh-nb.iam.gserviceaccount.comService account to impersonate after STS exchange.
--service-account-token-lifetime-seconds--service-account-token-lifetime-seconds=3600Requested lifetime of the impersonated access token.
--credential-source-field-name--credential-source-field-name=id_tokenJSON field name that contains the subject token when the source returns JSON.
--credential-source-headers--credential-source-headers=Key=ValueHeaders sent to a URL-based credential source.
--credential-source-type--credential-source-type=jsonDeclares whether the source format is JSON or plain text.
--sts-location--sts-location=us-central1Uses a regional STS endpoint instead of the global sts.googleapis.com endpoint.
--subject-token-type--subject-token-type=urn:ietf:params:oauth:token-type:jwtDeclares the subject token type expected from the external source.
--app-id-uri--app-id-uri=api://app-idAzure-only application ID URI.
--enable-imdsv2--enable-imdsv2Enforces AWS IMDSv2 in AWS-based configurations.
--executable-output-file--executable-output-file=C:\temp\cache.jsonCaches output from an executable token source.
--executable-timeout-millis--executable-timeout-millis=30000Maximum time allowed for the executable token source to finish.
--credential-cert-private-key-path--credential-cert-private-key-path=key.pemPrivate key path for certificate-based configurations.
--credential-cert-configuration-output-file--credential-cert-configuration-output-file=cert-config.jsonPath where the certificate helper configuration is written.
--credential-cert-trust-chain-path--credential-cert-trust-chain-path=chain.pemTrust chain file used when intermediate certificates exist.

ADC with WIF

WIF changes ADC behavior at the first search step. GOOGLE_APPLICATION_CREDENTIALS still wins, but the file can now be an external_account configuration instead of a service account key. The auth library reads the JSON, obtains the external token from the configured source, exchanges it with STS, and then impersonates the target service account.

Point GOOGLE_APPLICATION_CREDENTIALS at the WIF file in PowerShell

When code on Windows must use a WIF credential configuration file instead of local user ADC or a key file. It is typically triggered by CI runner setup, local reproduction of an external workload, or scripted testing of an external_account configuration. Session-local environment variable assignment in PowerShell. This is read by Google auth libraries in the current process and child processes. Make ADC choose the WIF credential configuration file at the first search-order step.

Set the PowerShell environment variable so ADC resolves the WIF credential configuration file first.

$env:GOOGLE_APPLICATION_CREDENTIALS = "C:\path\to\wif-cred-config.json"

Point GOOGLE_APPLICATION_CREDENTIALS at the WIF file in Linux

When code on Linux or macOS must use a WIF credential configuration file instead of local user ADC or a key file. It is typically triggered by container bootstrap, shell session setup, or CI runner initialization outside Google Cloud. Session-local shell environment variable assignment. Read by Google auth libraries in the current process and child processes. Make ADC choose the WIF credential configuration file at the first search-order step.

Set the shell environment variable so ADC resolves the WIF credential configuration file first.

export GOOGLE_APPLICATION_CREDENTIALS="/path/to/wif-cred-config.json"

The file pointed to by GOOGLE_APPLICATION_CREDENTIALS is still the highest-priority ADC source, but its meaning changes. With WIF, the file is not a credential by itself. It is an instruction set that tells the auth library how to obtain the real short-lived credential at runtime.

Current tooling supports WIF natively

Google documents WIF support in gcloud starting with Cloud SDK 363.0.0. The live environment used for this note runs 563.0.0, so both gcloud and current auth libraries can consume the generated external_account file format.

Gotchas and Edge Cases

Common sources of authentication failures in local development and CI/CD pipelines.

ADC token caching can delay new permissions

gcloud auth application-default login caches credential material in the local ADC file. If your IAM roles change after login, the old token or refresh cycle can leave you testing with stale permissions for up to an hour.

Refresh ADC after IAM changes

After updating IAM roles, re-run gcloud auth application-default login to pick up the new permissions immediately. In CI/CD, authenticate fresh on each run instead of reusing old ADC state.

  • gcloud auth login and gcloud auth application-default login create different credentials for different consumers.
  • Service account key files (key.json) do not expire on their own. If leaked, attackers keep access until the key is deleted.
  • On Compute Engine VMs and Cloud Run, the metadata server provides credentials automatically.

GCP Authentication with gcloud CLI References