Identity and Access

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

See the section bibliography for the reference material consulted while preparing these pages.

Commands and behaviour below are current for the gcloud CLI 586.x line, the Google Cloud Client Libraries for Java (libraries-bom), Spring Cloud GCP 8.x for Spring Boot 4 (7.4.x for Spring Boot 3) and the google-github-actions/auth action v3 (checked 2026-09-30); re-verify against gcloud version and the linked reference pages before relying on them in a pipeline.

Every Bookshelf resource (the catalog-api and orders-api Cloud Run services, the bookshelf-uploads-$PROJECT_ID bucket, the Secret Manager secrets and so on — see Google Cloud) is reached by something that first has to prove who it is and then be told what it may do. This page is that "who" and "what": Identity and Access Management (IAM) as the authorization model, service accounts and Workload Identity Federation for everything that is not a person, and Identity-Aware Proxy (IAP) for putting a sign-in in front of an application. The project and organization structure that IAM policies inherit through is in Resource Hierarchy and Projects; the account bootstrap is in Getting Started with Google Cloud.

IAM concepts

IAM answers one question for every API request: may this principal perform this permission on this resource? The answer comes from policies attached to the resource or to one of its ancestors in the resource hierarchy. IAM is a global service; policies are stored on the resource they protect, not in a region.

Principal

The "who". Principals are Google Accounts (people), Google groups, service accounts (workloads), Cloud Identity or Google Workspace domains, and federated identities from Workforce Identity Federation (people from an external identity provider) or Workload Identity Federation (workloads outside Google Cloud). The special values allUsers and allAuthenticatedUsers make a resource public and are best avoided.

Permission

A single allowed operation, written service.resource.verb, for example storage.buckets.list or run.services.invoke. Permissions are never granted directly.

Role

A named collection of permissions. A principal is granted a role, never an individual permission.

Allow policy

A list of bindings attached to a resource, each binding tying one role to a list of principals (optionally with a condition). Older documents call this simply an "IAM policy".

Resource

Anything with an IAM policy: an organization, a folder, a project, or a service resource such as a bucket, a Cloud Run service, a secret or a Pub/Sub topic.

The practical rule for Bookshelf: people are in Google groups that receive roles, workloads run as dedicated service accounts with narrow roles, CI authenticates through Workload Identity Federation, and no one downloads a service-account key.

Role types

Role type What it is When to use it

Basic (roles/owner, roles/editor, roles/viewer)

Thousands of permissions across nearly every service. Historically the only roles; they predate the others.

Throwaway projects only. Never grant them to a service account or to a production workload.

Predefined (roles/storage.objectUser, roles/run.invoker, …​)

Maintained by Google for one service and one job function, updated as the service gains permissions.

The default choice. Pick the narrowest one that covers the task.

Custom

A role you define from a list of permissions, at the organization or project level.

When no predefined role is narrow enough. You maintain it as permissions change.

Each role also has a launch stage and a list of permissions that can be checked before granting it, and not every permission can be used in a custom role (the role reference marks the exceptions).

Policy inheritance

Policies are inherited downwards: a binding on an organization applies to every folder, project and resource below it, and a binding on a project applies to every resource inside the project. The effective permissions of a principal are the union of the allow bindings on the resource and all its ancestors. A child policy can add access but cannot remove what a parent granted; to take access away you use a deny policy (below), not an allow policy lower down.

flowchart TD O[Organization
binding: group admins] --> F[Folder bookshelf
binding: group developers] F --> P[Project my-project-id
binding: ci service account] P --> B[Bucket bookshelf-uploads
binding: catalog-api service account] P --> S[Cloud Run service catalog-api
binding: IAP service agent] B --> E[Effective access =
union of every binding above and on the resource]

Bind at the lowest level that makes sense: a role on one bucket or one secret beats the same role on the project.

Granting roles

In the Google Cloud console

  1. Select the project (or folder, or organization) in the project picker, then open IAM & Admin → IAM.

  2. Click Grant access, enter the principal (an email, a group or a service-account address) and choose a role. The role selector filters by product and shows the permission list of the selected role.

  3. Optionally click Add IAM condition to restrict the binding (see below), then Save.

  4. To grant on a single resource, open that resource (for example a bucket’s Permissions tab) instead.

  5. To check what a principal can already do, use IAM & Admin → Policy Troubleshooter, or open IAM and switch to the View by roles tab.

  6. Open the Recommendations column or the Role Recommender hints to see roles that can be reduced.

With the gcloud CLI

gcloud services enable iam.googleapis.com iamcredentials.googleapis.com \
  sts.googleapis.com cloudresourcemanager.googleapis.com \
  recommender.googleapis.com

PROJECT_ID=$(gcloud config get-value project)
BUCKET=bookshelf-uploads-$PROJECT_ID

# Organization, folder and project level
gcloud organizations add-iam-policy-binding ORGANIZATION_ID \
  --member="group:platform-admins@example.com" \
  --role="roles/resourcemanager.folderAdmin"
gcloud resource-manager folders add-iam-policy-binding FOLDER_ID \
  --member="group:bookshelf-devs@example.com" \
  --role="roles/run.developer"
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
  --member="group:bookshelf-devs@example.com" \
  --role="roles/logging.viewer"

# A single resource: the least-privilege option
gcloud storage buckets add-iam-policy-binding "gs://$BUCKET" \
  --member="serviceAccount:catalog-api@$PROJECT_ID.iam.gserviceaccount.com" \
  --role="roles/storage.objectUser"

# Inspect and review
gcloud projects get-iam-policy "$PROJECT_ID" --format=yaml
gcloud iam roles describe roles/storage.objectUser

Every add-iam-policy-binding reads the policy, edits it and writes it back with its etag, so two concurrent callers do not silently overwrite each other. Prefer add-iam-policy-binding and remove-iam-policy-binding over set-iam-policy with a hand-edited file, which replaces the whole policy.

Custom roles

A custom role is created from a list of permissions. The smallest useful example is a role that can only list the buckets of a project (storage.buckets.list), for example for an audit tool:

gcloud iam roles create bucketLister \
  --project="$PROJECT_ID" \
  --title="Bucket Lister" \
  --description="List buckets in the project, nothing else" \
  --permissions=storage.buckets.list \
  --stage=GA

gcloud projects add-iam-policy-binding "$PROJECT_ID" \
  --member="serviceAccount:auditor@$PROJECT_ID.iam.gserviceaccount.com" \
  --role="projects/$PROJECT_ID/roles/bucketLister"

In the console use IAM & Admin → Roles → Create role, or Create role from selection after ticking existing roles. Custom roles are a maintenance burden (new permissions in a service do not appear in them) and have per-project and per-organization quotas, so prefer a predefined role when one fits.

IAM Conditions

A binding can carry a Common Expression Language condition so that it applies only to some resources or at some times. For example, allow the Bookshelf notifier service account to read objects only under a prefix:

gcloud storage buckets add-iam-policy-binding "gs://$BUCKET" \
  --member="serviceAccount:notifier@$PROJECT_ID.iam.gserviceaccount.com" \
  --role="roles/storage.objectViewer" \
  --condition='expression=resource.name.startsWith("projects/_/buckets/'"$BUCKET"'/objects/covers/"),title=covers-prefix-only'

Conditions can also test time (temporary access), resource tags, or the resource type, and conditional bindings require policy version 3.

Deny policies, boundaries and just-in-time access

Allow policies only add access. Three further features narrow it or make it temporary:

  • Deny policies are attached to an organization, folder or project and block named permissions for named principals regardless of any allow policy. They are evaluated before allow policies, so they are the tool for guardrails such as "nobody outside the platform group may delete projects".

  • Principal Access Boundary policies define the set of resources a group of principals is even eligible to access, independent of the roles they hold.

  • Privileged Access Manager grants time-bound, approved elevation, so people hold no standing privileged roles.

Each is created with its own gcloud iam command group (for example gcloud iam policies create for deny policies); see the linked pages for the syntax. Together with the allow policy, the order of evaluation is: deny policies first, then principal access boundaries, then allow policies.

The IAM recommender closes the loop: it compares granted permissions with the permissions actually used over the last 90 days and suggests narrower roles.

gcloud recommender recommendations list \
  --project="$PROJECT_ID" --location=global \
  --recommender=google.iam.policy.Recommender \
  --format="table(name.basename(),description)"

Service accounts

A service account is a principal for a workload rather than a person. It has an email address of the form NAME@PROJECT_ID.iam.gserviceaccount.com and is both an identity (granted roles on resources) and a resource (other principals are granted roles on it, such as permission to use or impersonate it).

Three ways exist for code to act as a service account, in order of preference:

  1. Attach the service account to the compute resource, so the platform supplies short-lived tokens.

  2. Impersonate the service account from another identity, producing short-lived tokens on request.

  3. Create and download a key — a long-lived secret. This is the last resort.

Default and user-managed service accounts

Some services create a default service account on first use (for example the Compute Engine default service account). In older projects these were granted the broad roles/editor role automatically. Organizations created on or after 2024-05-03 are secure by default, and the constraint iam.automaticIamGrantsForDefaultServiceAccounts stops that grant, so a workload on a default service account in such an organization has no permissions until you give it some. Create one dedicated user-managed service account per workload instead (catalog-api, orders-api, notifier) and grant it only the roles it needs.

Creating and attaching a service account

In the Google Cloud console

  1. Open IAM & Admin → Service Accounts → Create service account.

  2. Enter a name and ID (the ID is permanent and forms the email address) and click Create and continue.

  3. Add the roles it needs on the project (preferably leave this empty and grant on individual resources later) and click Done.

  4. When creating a Cloud Run service, a Compute Engine VM or a GKE node pool, choose this account in its Security (or Service account) setting. That is "attaching" it.

With the gcloud CLI

gcloud iam service-accounts create catalog-api \
  --display-name="Bookshelf catalog-api runtime"

SA=catalog-api@$PROJECT_ID.iam.gserviceaccount.com

# Narrow roles on specific resources
gcloud storage buckets add-iam-policy-binding "gs://$BUCKET" \
  --member="serviceAccount:$SA" --role="roles/storage.objectUser"
gcloud secrets add-iam-policy-binding catalog-db-password \
  --member="serviceAccount:$SA" --role="roles/secretmanager.secretAccessor"

# Attach it to a Cloud Run service
gcloud run services update catalog-api --region="$REGION" \
  --service-account="$SA"

Whoever attaches a service account to a resource needs the permission to act as it (iam.serviceAccounts.actAs, included in roles/iam.serviceAccountUser). That is deliberate: a developer who can deploy a service must also be trusted with every permission of the account the service runs as. Grant roles/iam.serviceAccountUser on the single service account, not on the project.

Impersonation

Impersonation lets an identity that holds roles/iam.serviceAccountTokenCreator on a service account request short-lived tokens for it. It is the keyless way to run a command or a test with a workload’s exact permissions, and the building block behind Workload Identity Federation for CI.

# Allow a developer group to impersonate the service account
gcloud iam service-accounts add-iam-policy-binding "$SA" \
  --member="group:bookshelf-devs@example.com" \
  --role="roles/iam.serviceAccountTokenCreator"

# One command as the service account
gcloud storage ls "gs://$BUCKET" --impersonate-service-account="$SA"

# Or for the whole gcloud configuration
gcloud config set auth/impersonate_service_account "$SA"

# Application Default Credentials for local code, impersonating the account
gcloud auth application-default login --impersonate-service-account="$SA"

The token the impersonated call yields lasts one hour by default. Because every impersonation is written to the audit logs against both the caller and the service account, it is also the answer to "who really did this?".

Service-account keys

A service-account key is a JSON file containing a private key that anyone holding the file can use until it is deleted. Older guides still show downloading one for local development or for a CI pipeline. Today it is the last resort, and in organizations created on or after 2024-05-03 it fails by default: the managed constraints iam.managed.disableServiceAccountKeyCreation and iam.disableServiceAccountKeyUpload are enforced, so gcloud iam service-accounts keys create returns a policy error.

# See whether key creation is blocked for the project
gcloud org-policies describe iam.disableServiceAccountKeyCreation \
  --project="$PROJECT_ID" --effective

Do not lift that constraint to make a tutorial work. Use the replacements instead: an attached service account for code running on Google Cloud, impersonation for a developer machine, and Workload Identity Federation for anything running elsewhere. Where a key truly cannot be avoided (a third-party system with no federation support), scope the exception to a single project, give the key a short expiry with iam.serviceAccountKeyExpiryHours, store it in Secret Manager (see Secret Manager and Cloud KMS) and rotate it.

Workload Identity Federation

Workload Identity Federation lets a workload that runs outside Google Cloud (GitHub Actions, GitLab CI, AWS, Azure, an on-premises OIDC or SAML provider) exchange its own identity token for a short-lived Google Cloud access token, with no secret stored on the workload. A workload identity pool holds external identities, and a provider in the pool describes how to trust one issuer and how to map its token claims into attributes. GKE has its own variant, Workload Identity Federation for GKE, covered in GKE Clusters.

sequenceDiagram participant J as CI job (GitHub Actions or GitLab) participant IdP as CI OIDC issuer participant STS as Google Security Token Service participant IAM as IAM Credentials API participant GCS as Cloud Storage J->>IdP: Request OIDC token for this job IdP-->>J: Signed JWT (repository, ref, workflow claims) J->>STS: Exchange JWT, pool and provider name STS->>IdP: Fetch public keys and verify signature STS-->>J: Federated access token J->>IAM: generateAccessToken for the service account IAM-->>J: Short-lived service account access token J->>GCS: API call with the access token GCS-->>J: Response after IAM allow check

In the simplest form (direct Workload Identity Federation) the second token step is skipped: the roles are granted straight to the federated principal, and the federated token is used as is. Impersonating a service account adds a step but works with every product, including those that do not accept federated principals directly.

Setting up a pool for GitHub Actions

The attribute mapping decides what identity the pool exposes, and the attribute condition decides which tokens are accepted at all. Always set a condition: without one, any GitHub repository in the world could present a token to your pool.

In the Google Cloud console

  1. Open IAM & Admin → Workload Identity Federation → Create pool.

  2. Name the pool (for example github), click Continue, choose the provider type OpenID Connect (OIDC), name the provider and set the Issuer (URL) to https://token.actions.githubusercontent.com.

  3. Under Configure provider attributes map google.subject to assertion.sub, add attribute.repository = assertion.repository and attribute.repository_owner = assertion.repository_owner, and set an attribute condition that restricts the organization.

  4. Save, then use Grant access on the pool to bind a service account or a role to the principal that matches your repository.

With the gcloud CLI

PROJECT_NUMBER=$(gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)')

gcloud iam workload-identity-pools create github \
  --project="$PROJECT_ID" --location=global \
  --display-name="GitHub Actions"

gcloud iam workload-identity-pools providers create-oidc bookshelf-repo \
  --project="$PROJECT_ID" --location=global \
  --workload-identity-pool=github \
  --issuer-uri="https://token.actions.githubusercontent.com/" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner" \
  --attribute-condition="assertion.repository_owner=='my-org'"

# Let the bookshelf/catalog-api repository impersonate the deployer service account
gcloud iam service-accounts add-iam-policy-binding \
  "deployer@$PROJECT_ID.iam.gserviceaccount.com" \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/github/attribute.repository/my-org/catalog-api"

The documentation recommends matching on numeric identifiers (for example the repository owner ID) rather than names where the provider offers them, so a deleted and re-registered name cannot inherit trust.

A GitHub Actions workflow then needs the id-token: write permission and one authentication step. The action is v3 at the time of writing; use the current major version listed in its repository:

name: deploy
on:
  push:
    branches: [main]

permissions:
  contents: read
  id-token: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - id: auth
        uses: google-github-actions/auth@v3
        with:
          project_id: my-project-id
          workload_identity_provider: projects/<project-number>/locations/global/workloadIdentityPools/github/providers/bookshelf-repo
          service_account: deployer@my-project-id.iam.gserviceaccount.com
      - uses: google-github-actions/setup-gcloud@v3
      - run: gcloud run deploy catalog-api --source . --region us-central1

The workflow holds no secret at all: the only values are resource names. The broader workflow structure (build, tests, image publishing, environments) is in Docker CI/CD with GitHub Actions; this page covers the Google Cloud side. Container images pushed from the workflow go to Artifact Registry, see Container Registry and Builds.

GitLab CI

GitLab issues an OIDC token to each job through the id_tokens keyword. The provider points at the GitLab issuer (https://gitlab.com for gitlab.com, or your instance URL) and conventionally maps the project and namespace IDs:

gcloud iam workload-identity-pools create gitlab \
  --project="$PROJECT_ID" --location=global

gcloud iam workload-identity-pools providers create-oidc bookshelf-group \
  --project="$PROJECT_ID" --location=global \
  --workload-identity-pool=gitlab \
  --issuer-uri="https://gitlab.com" \
  --attribute-mapping="google.subject=assertion.sub,attribute.project_id=assertion.project_id,attribute.namespace_id=assertion.namespace_id" \
  --attribute-condition="assertion.namespace_id=='GITLAB_GROUP_ID'"

The job declares an id_tokens entry whose audience is the provider’s full resource name, then exchanges that token as in the diagram above. GitLab’s own ID token authentication page documents the job side.

Other workloads outside Google Cloud

The same pool and provider structure serves AWS (an AWS provider type), Azure, and any OIDC or SAML issuer, and Workforce Identity Federation does the equivalent for people signing in with an external identity provider instead of a Google Account. For GKE pods, the equivalent is Workload Identity Federation for GKE, which can grant roles directly to a Kubernetes service account principal without a Google service account.

Application Default Credentials in code

Application Default Credentials (ADC) is the lookup strategy the Google client libraries use so the same code runs locally and in production: they check the GOOGLE_APPLICATION_CREDENTIALS environment variable, then the credentials written by gcloud auth application-default login, then the attached service account supplied by the metadata server. In Cloud Run, GKE and Compute Engine this means the code needs no credential configuration at all.

package com.example.bookshelf;

import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;

public final class StorageFactory {

    private StorageFactory() {
    }

    // ADC: attached service account in production, the developer's own
    // (or impersonated) credentials locally
    public static Storage create() throws java.io.IOException {
        GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
        return StorageOptions.newBuilder()
            .setCredentials(credentials)
            .build()
            .getService();
    }
}

With Spring Cloud GCP the starters build their clients from the same ADC chain, so a Spring Boot service that uses the Cloud Storage or Secret Manager starter needs only a project ID, and no key file property. For the Spring side of storage and configuration see File Storage & Object Stores and Configuration and Profiles.

Where code must call an API as a different service account than the one it runs as (for example a signed URL signed by a dedicated identity), use ImpersonatedCredentials on top of the ADC source credentials rather than a key.

Identity-Aware Proxy

Identity-Aware Proxy (IAP) puts Google sign-in and an IAM check in front of an application, so the application does not implement login itself. A request only reaches the backend if the user is authenticated and holds the IAP-secured Web App User role (roles/iap.httpsResourceAccessor) on the protected resource. IAP itself is offered at no additional charge.

sequenceDiagram participant U as User browser participant IAP as Identity-Aware Proxy participant IAM as IAM participant App as Backend (Cloud Run, load balancer backend, GKE, App Engine) U->>IAP: HTTPS request IAP-->>U: Redirect to Google sign-in if no session U->>IAP: Request with sign-in cookie IAP->>IAM: Does the user hold iap.httpsResourceAccessor? IAM-->>IAP: Allow IAP->>App: Request plus signed JWT header App-->>U: Response

IAP can protect App Engine, backends behind an external or internal Application Load Balancer, GKE services (configured on a GCPBackendPolicy with Gateway API; older guides show an Ingress BackendConfig), Compute Engine and, since 2026-03-13 (generally available), Cloud Run services directly. Before that date IAP in front of Cloud Run required a load balancer; direct integration protects the default run.app URL and every other ingress path with one setting, and IAP cannot be configured on both the load balancer and the Cloud Run service. IAP also provides TCP forwarding for SSH and RDP to VMs without public IPs, which is how Bookshelf administrators reach private instances; see VPN and Hybrid Connectivity.

Enabling IAP on Cloud Run

In the Google Cloud console

  1. Open Cloud Run, select the service and open the Security tab.

  2. Under Authentication, choose Require authentication and tick Identity-Aware Proxy (IAP), then save. In a project that is not in an organization, first configure the OAuth consent screen and an OAuth client when prompted.

  3. Open Security → Identity-Aware Proxy, select the Cloud Run service, click Add principal and grant IAP-secured Web App User to the people or groups who may sign in.

With the gcloud CLI

gcloud services enable iap.googleapis.com run.googleapis.com

gcloud run services update orders-api --region="$REGION" --iap

# IAP calls the service as its own service agent, so it needs the invoker role
gcloud run services add-iam-policy-binding orders-api --region="$REGION" \
  --member="serviceAccount:service-$PROJECT_NUMBER@gcp-sa-iap.iam.gserviceaccount.com" \
  --role="roles/run.invoker"

# Who may sign in
gcloud iap web add-iam-policy-binding \
  --member="group:bookshelf-staff@example.com" \
  --role="roles/iap.httpsResourceAccessor" \
  --region="$REGION" --resource-type=cloud-run --service=orders-api

For a new service pass --no-allow-unauthenticated --iap to gcloud run deploy. The service’s own invoker setting and the other Cloud Run authentication options are covered in Cloud Run.

Trust the signed header, not the plain ones

IAP forwards the identity to the application in headers. X-Goog-Authenticated-User-Email and X-Goog-Authenticated-User-Id are convenient, but they are plain headers: anything that can reach the backend without going through IAP (another service in the VPC, a misconfigured firewall, a service with a stale ingress setting) can send forged values. The header to trust is X-Goog-IAP-JWT-Assertion, a JWT signed by IAP with ES256, whose issuer is https://cloud.google.com/iap and whose audience identifies the protected resource (for Cloud Run /projects/PROJECT_NUMBER/locations/REGION/services/SERVICE_NAME). A Spring Boot servlet filter that verifies it:

package com.example.bookshelf.security;

import com.google.api.client.json.webtoken.JsonWebToken;
import com.google.auth.oauth2.TokenVerifier;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.web.filter.OncePerRequestFilter;

public class IapJwtFilter extends OncePerRequestFilter {

    private static final String ISSUER = "https://cloud.google.com/iap";

    private final TokenVerifier verifier;

    public IapJwtFilter(String expectedAudience) {
        this.verifier = TokenVerifier.newBuilder()
            .setAudience(expectedAudience)
            .setIssuer(ISSUER)
            .build();
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        String jwt = request.getHeader("X-Goog-IAP-JWT-Assertion");
        if (jwt == null) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }
        try {
            JsonWebToken verified = verifier.verify(jwt);
            request.setAttribute("iap.email", verified.getPayload().get("email"));
            chain.doFilter(request, response);
        } catch (TokenVerifier.VerificationException e) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
        }
    }
}

The same filter can be turned into a Spring Security authentication filter. For general application-level security, see Spring Security and, for Quarkus, Security Overview and Architecture.

Identity Platform

IAP authenticates your staff with Google identities. For customers of an application, Identity Platform (the Google Cloud product that underlies Firebase Authentication) is a managed customer identity service: email and password, phone, social sign-in, SAML and OIDC providers, multi-factor authentication, and multi-tenancy. It issues standard ID tokens that a backend verifies with the Firebase Admin SDK or any OIDC library. Choose it when Bookshelf needs customer sign-up without running its own identity server; choose an identity server such as the one described in Authorization Server & Social Login when you need full control of the OAuth 2.0 and OpenID Connect behaviour.

Identity Platform is enabled per project with gcloud services enable identitytoolkit.googleapis.com and configured in the console under Identity Platform → Providers; pricing is per monthly active user with a no-cost tier, so check the pricing page before launch.

For the protocol side, see OAuth Reference, OpenID Connect, Social Login and Federation and JWT and the JOSE Family. Verifying tokens in Quarkus is in JWT, OIDC and Keycloak.

Pricing, quotas and launch stages

  • Cost drivers: IAM, service accounts, Workload Identity Federation, deny policies and IAP are free. Identity Platform is billed per monthly active user beyond its free tier. Privileged Access Manager and some Security Command Center features sit in paid tiers. See the Identity Platform pricing page and re-check it before launch.

  • Quotas: service accounts per project, custom roles per project and organization, and members per policy are limited; the IAM quotas page lists current values and the console shows them under IAM & Admin → Quotas.

  • Launch stages: direct IAP on Cloud Run generally available since 2026-03-13; managed service-account key constraints enforced by default from 2024-05-03 for new organizations. Workload Identity Federation, deny policies, Principal Access Boundary and Privileged Access Manager are available features; confirm the launch stage on each product page before designing around it.

  • Policy propagation: IAM changes are eventually consistent and usually effective within about two minutes, but can take longer; retry a transient PERMISSION_DENIED after a fresh grant before debugging further.

Troubleshooting

  • PERMISSION_DENIED: run the Policy Troubleshooter (console) or gcloud policy-intelligence troubleshoot-policy iam with the principal, resource and permission to see which binding, deny policy or boundary decided the outcome.

  • iam.serviceAccounts.actAs denied on deploy: the deploying principal lacks roles/iam.serviceAccountUser on the runtime service account.

  • Permission 'iam.serviceAccounts.getAccessToken' denied: the caller lacks roles/iam.serviceAccountTokenCreator on the target, or in Workload Identity Federation the principal lacks roles/iam.workloadIdentityUser.

  • The given credential is rejected by the attribute condition: the token claims do not satisfy the provider’s attribute condition; decode the token from a failing job and compare.

  • Key creation is not allowed on this service account: the organization policy blocks keys, which is intended; switch to one of the keyless methods above.

  • IAP returns 403 after sign-in: the user lacks roles/iap.httpsResourceAccessor on that resource, or the IAP service agent lacks roles/run.invoker.

Clean up

Remove what this page created, in dependency order, in a throwaway project:

gcloud run services update orders-api --region="$REGION" --no-iap  # or clear it in the Security tab
gcloud iam workload-identity-pools providers delete bookshelf-repo \
  --project="$PROJECT_ID" --location=global --workload-identity-pool=github
gcloud iam workload-identity-pools delete github \
  --project="$PROJECT_ID" --location=global
gcloud iam roles delete bucketLister --project="$PROJECT_ID"
gcloud iam service-accounts delete "$SA"

Deleted pools, providers, custom roles and service accounts enter a soft-deleted state and can be undeleted for a limited period (the retention differs per resource type; see the linked reference pages), and their IDs may not be reusable immediately; use new IDs when repeating the exercise. Role bindings that referenced a deleted service account stay in policies as deleted:serviceAccount:…​ entries until removed. Deleting the whole throwaway project removes everything at once (see Resource Hierarchy and Projects).