A Cloud Run service can now be configured so that no unauthenticated request reaches the container, without running a load balancer in front of it.
Google's direct IAP integration for Cloud Run reached general availability on March 13, 2026 [1]. IAP intercepts every inbound HTTPS request, runs the OAuth flow against Google identity, validates the caller against an IAM policy, and only then forwards the request to the container. For personal apps holding real credentials, this moves authentication off application code and out of the load-balancer stack that previously made the pattern impractical.
Until the GA, applying this pattern required provisioning an HTTPS Application Load Balancer, a serverless NEG, a backend service, a static IP, and a managed TLS certificate. The GA reduces that stack to a Cloud Run-native setting plus IAM: enable IAP on the service, require authentication, grant the IAP service agent invoker, and grant authorized users IAP access. IAP itself has no additional charge for Google Cloud-hosted resources, though some advanced capabilities (customization, device attributes, custom access levels, non-Google Cloud resources) are paid features of Chrome Enterprise Premium [2].
Cloud Run IAM enforces authentication at the service invocation level: the caller must hold roles/run.invoker on the service. That works for service-to-service calls but not for human browser sessions, because there is no OAuth flow to obtain user credentials. IAP layers an OAuth 2.0 flow against Google identity in front of that check, redirects unauthenticated browsers to Google Sign-In, and forwards the validated request to the container with signed identity headers. Cloud Run IAM remains the right control for service-to-service traffic; IAP is the right control for human access. This article covers the human-access path.
For a single-user deployment, the IAM policy holds one binding: one Google account email, one role, one service. That is the entire authorization surface at the front door.
Prerequisite: the OAuth consent screen. The first time IAP is enabled in a project, Google prompts to configure an OAuth consent screen. By default, direct IAP on Cloud Run uses a Google-managed OAuth client for in-organization users [3]. For a Workspace-owned project where the only authorized identity is in the same organization (the scope of this post), this is mostly a one-time setup step and otherwise invisible. Outside-org users or projects without an organization may require Console setup or a custom OAuth client.
One structural limit deserves treatment before committing to this pattern: inbound webhooks. Stripe events, Pub/Sub push subscriptions, and most third-party notification services cannot perform browser-based OAuth. Three patterns resolve this, covered in detail below: service account OIDC tokens for GCP-native callers, a separate receptor service for external providers, and load-balancer path routing reserved for edge cases. IAP policies run before Cloud Run IAM, so a Pub/Sub push subscription authenticating against Cloud Run IAM rather than IAP fails at the intercept layer [4].
The March 2026 GA dropped the dollar cost of this pattern to near zero. Operational cost is small but not zero: an OAuth consent screen at first project setup, IAM binding hygiene, optional JWT validation in app code, and webhook exception plumbing for any provider that cannot send OIDC tokens. Three things to manage going forward: the user IAM binding, the IAP service agent binding, and OAuth consent screen state.
This pattern enforces who can reach the service, not fine-grained authorization within it. Per-route authorization, role-based access, and resource-level policy remain the application's responsibility. IAP is the front door; it does not replace internal authorization logic. In practice, the resulting deployment is materially simpler than the load-balancer-plus-IAP setup it replaces: fewer components, fewer surfaces to audit, fewer pieces to wire.
The pattern is GCP-specific. Equivalent setups on AWS use ALB + Cognito or API Gateway authorizers; on Azure, Application Gateway with AAD authentication. The architectural goal (terminate identity at the edge, before application code) carries across platforms; the components do not.
For any personal app holding credentials or personal data, that trade-off is worth making.
Deployment: New Service and Existing Service
The --iap flag is now available in the standard gcloud run command surface [10]. Run gcloud components update before using the commands below.
Deploying a new Cloud Run service with IAP enabled uses two flags. The --no-allow-unauthenticated flag prevents direct invocation via Cloud Run IAM; --iap enables the IAP intercept layer. Together these flags ensure no unauthenticated internet path reaches the container when ingress is set appropriately:
gcloud run deploy SERVICE_NAME \
--region=REGION \
--image=IMAGE_URL \
--no-allow-unauthenticated \
--iap
For an existing service, a single update command enables IAP without redeployment:
gcloud run services update SERVICE_NAME \
--region=REGION \
--iap
After either command, the IAP service agent needs permission to forward authenticated traffic to the container. Grant roles/run.invoker to the IAP service account, where PROJECT_NUMBER is the numeric project ID:
gcloud run services add-iam-policy-binding SERVICE_NAME \
--region=REGION \
--member=serviceAccount:[email protected] \
--role=roles/run.invoker
The Terraform equivalent sets iap_enabled = true on the google_cloud_run_v2_service resource and adds the same IAM binding. The Console exposes the same configuration as a checkbox under the Security tab.
Grant a single user access with roles/iap.httpsResourceAccessor scoped to the service, not the project. Scoping to the service is the minimum-privilege approach:
gcloud iap web add-iam-policy-binding \
--member=user:YOUR_EMAIL \
--role=roles/iap.httpsResourceAccessor \
--region=REGION \
--resource-type=cloud-run \
--service=SERVICE_NAME
IAM binding changes take 5 to 10 minutes to propagate through IAP's policy cache. A grant that looks correct in the Console but still returns 403 is usually waiting on cache, not misconfigured. Plan deployment windows accordingly.
IAP secures all requests that pass through its enforcement layer before reaching the Cloud Run service, including requests to the default *.run.app URL. One layering constraint: IAP cannot be enabled on both the Cloud Run service and a load balancer fronting it [3]. If a load balancer sits in front of Cloud Run, IAP belongs on one of them, not both. For a single-user deployment without a load balancer, IAP on the Cloud Run service directly is the correct configuration.
Custom Domains
Cloud Run domain mappings should preserve the same service-level IAP enforcement because IAP is attached to the Cloud Run service, not only the default *.run.app hostname. For single-user deployments, this keeps the no-load-balancer model intact: map the custom domain to the IAP-protected service and test the mapped hostname directly.
I would still treat this as a deployment check rather than an assumption, especially if using a custom OAuth client, out-of-org access, or multiple hostnames. If the application needs path routing, shared edge controls, or more explicit hostname behavior, the External Application Load Balancer path remains available, but it reintroduces the load-balancer stack this pattern avoids.
Audit Logs
IAP writes access decisions to Cloud Audit Logs under the iap.googleapis.com service. Authentication decisions appear as Data Access logs; configuration changes appear as Admin Activity logs. Admin Activity logs are always on; Data Access logs are off by default and must be enabled.
To enable Data Access logs for IAP, edit the project IAM policy directly:
gcloud projects get-iam-policy PROJECT_ID > /tmp/policy.yaml
# Add to /tmp/policy.yaml under auditConfigs:
# - service: iap.googleapis.com
# auditLogConfigs:
# - logType: ADMIN_READ
# - logType: DATA_READ
# - logType: DATA_WRITE
gcloud projects set-iam-policy PROJECT_ID /tmp/policy.yaml
For a single-user deployment, this turns IAP into a usable audit trail of who reached the service and when.
What IAP Does at the Request Level
IAP intercepts every inbound HTTPS request before it reaches the container and runs a full OAuth 2.0 flow. Unauthenticated requests receive a Google Sign-In redirect. After sign-in, IAP validates the caller against the roles/iap.httpsResourceAccessor IAM binding and forwards the request with two headers injected by Google's infrastructure [5]. The OAuth flow runs once per session, not per request. Subsequent requests within the session validate against the existing IAP cookie at Google's edge, which is usually acceptable for interactive personal tools. It is still an extra enforcement hop, so latency-sensitive services should measure before adopting it [3].
X-Goog-Authenticated-User-Email- the verified email of the callerX-Goog-IAP-JWT-Assertion- a signed JWT from Google for cryptographic verification
The plain email header is convenient but spoofable on any request path that bypasses IAP. With --no-allow-unauthenticated set and IAP enabled, there should be no such path under normal configuration: Cloud Run IAM rejects direct invocations, and IAP terminates everything that reaches the service. The signed JWT validation is belt-and-suspenders against future misconfiguration: if someone toggles --allow-unauthenticated, adds a misconfigured load balancer, or grants over-broad IAM, the JWT check fails closed on any request that did not actually pass through IAP. It is not a substitute for IAP. It is insurance that the configuration stays the configuration.
Webhook Exception Patterns
Inbound webhooks create a structural tension in this model. Payment events, third-party push notifications, and most provider callbacks cannot perform browser-based OAuth, so they cannot pass through IAP's standard authentication flow. Three patterns resolve this, each with different tradeoffs.
Pattern 1: Service account OIDC tokens for GCP-native callers. Pub/Sub, Cloud Scheduler, Cloud Tasks, and other Cloud Run services can authenticate using a service account OIDC identity token. Grant the service account roles/iap.httpsResourceAccessor. The OIDC token's audience must be the IAP OAuth client ID for the service, not the Cloud Run URL [4]. This is the most common configuration mistake: a Pub/Sub push subscription that targets the Cloud Run URL as the OIDC audience will fail at the IAP layer, because IAP validates the token audience against its OAuth client ID before forwarding the request. Configure the push subscription's authentication with audience set to the IAP OAuth client ID.
Pattern 2: Separate unauthenticated receptor service. For external providers that cannot send OIDC tokens, deploy a minimal Cloud Run service with --allow-unauthenticated. That receptor validates the inbound webhook using the provider's signature mechanism (HMAC or shared secret) and enqueues the payload to Pub/Sub or Cloud Tasks. The IAP-protected service processes the queued payload. The receptor's attack surface is intentionally minimal: validate and enqueue, nothing else.
Pattern 3: Path-based routing via load balancer. Route a dedicated /webhooks path to a backend service without IAP, while all other paths remain protected. This requires reintroducing a load balancer, which defeats the simplicity of the direct-IAP model for those routes. Reserve this pattern for cases where neither OIDC tokens nor a receptor service fits the provider's constraints.
The webhook exception is the one place where this model adds complexity rather than removing it. For services with no external webhook surface, the direct-IAP pattern stays clean. For services with even one third-party callback, the operator picks up either an OIDC audience configuration step or a separate receptor service to maintain.
The Pub/Sub and IAP Intercept Order
GCP documents that IAP policies run before Cloud Run IAM checks [4]. For IAP-protected programmatic callers, configure the OIDC audience for the IAP resource and client expected by IAP, not the Cloud Run service URL. A Pub/Sub push subscription configured with OIDC authentication aimed at Cloud Run IAM (audience = Cloud Run service URL) is intercepted by IAP first, and the audience mismatch causes the request to fail at the IAP layer. Pattern 1 and Pattern 2 both produce the same end state: a valid identity reaching the application layer.
CORS Preflight as a Special Case
Browsers send an HTTP OPTIONS preflight request before cross-origin calls. These requests carry no authentication credentials, so IAP rejects them by default. The direct IAP on Cloud Run GA release supports native CORS preflight handling: unauthenticated OPTIONS is allowed to satisfy the browser negotiation while all other methods continue to require IAP authentication [1]. Set access_settings.cors_settings.allow_http_options: true in the IAP settings, and configure the application to respond to OPTIONS requests with the appropriate Access-Control-* headers.
This should be treated as a narrow preflight exception, not an application data path. Misconfigured CORS on IAP-protected apps can turn preflight handling into an exfiltration channel, so OPTIONS responses should contain only the headers required for browser negotiation [7].
Threat Model Limits
IAP is an authentication perimeter, not a Web Application Firewall, not an intrusion detection system, and not a guarantee of application security. Table 1 summarizes the limits a deployer should understand and the mitigations that close each gap.
Table 1. Threat-model limits for IAP on Cloud Run and recommended mitigations.
| Limit | Description | Mitigation |
|---|---|---|
| Post-auth application vulnerabilities | IAP authenticates callers; it does not audit what authenticated callers do. SQL injection, XSS, and SSRF operate entirely inside the authenticated session. | Validate all inputs; apply SSRF defenses at the application layer; restrict outbound egress where possible. |
| Compromised Google account | The trust anchor is the Google account holding roles/iap.httpsResourceAccessor. Phishing or credential stuffing breaks the model at its root. |
Hardware security key MFA; Context-Aware Access policies (IP range, device posture) add layers, but advanced context-aware capabilities require Chrome Enterprise Premium [2]. |
| Signed header spoofing if IAP is bypassed | IAP injects X-Goog-Authenticated-User-Email. Any path to the container that bypasses IAP allows header forgery. |
Validate X-Goog-IAP-JWT-Assertion cryptographically in the application [5]. Set ingress to internal or internal-and-cloud-load-balancing if the deployment topology allows. |
| Supply chain and dependency risks | IAP does not inspect container code or dependencies. A compromised npm package or Docker base image layer runs below the authentication boundary. | Dependency scanning, base image pinning, artifact registry vulnerability alerts. |
| Metadata server exposure | Every Cloud Run service has access to http://metadata.google.internal. SSRF in an app whose service account holds broad permissions can exfiltrate credentials or signed tokens. |
Dedicate one least-privilege service account per Cloud Run service [8]. Grant only the exact IAM permissions that service requires. |
BeyondCorp, Scrutinized
The single-user pattern above is simple enough to deploy in minutes. The harder question is what security model it actually buys.
The pattern uses IAP and inherits Google's BeyondCorp model. Both deserve scrutiny: as the security posture of a personal app, and as the strategic framing enterprise IT has spent a decade buying.
For personal stacks
The trust anchor reduces to one Google account plus its second factor. No second control plane, no out-of-band check, no n-of-m approval. If the account is phished, the signing key on the security key is stolen, or Google's account recovery flow is socially engineered, every IAP-protected service falls at once. IAP is one control, not a whole security model. The hardening that matters runs alongside it: signed JWT validation in every handler, one least-privilege service account per service, secret rotation discipline, dependency hygiene, and ingress locked to internal-and-cloud-load-balancing where the topology allows. It gets the perimeter right; the posture needs the rest of that list.
For enterprise stacks
The enterprise framing deserves a harder look, because "zero-trust" has become a vendor narrative that obscures real architectural debt.
Identity-only deployments branded as BeyondCorp. BeyondCorp's value proposition is identity plus context: who you are, what device, what posture, what behavior. The context half requires Chrome Enterprise Premium, BeyondCorp Enterprise, Endpoint Verification, a managed device fleet that actually reports posture, and headcount to maintain Context-Aware Access policies. Many IAP deployments risk becoming identity-only deployments with stale or absent CAA policies. That is strong perimeter authentication, materially better than VPN, but it is not what the marketing claimed.
Vendor concentration as the actual decision. IAP + Workspace + Cloud Identity + Chrome Enterprise Premium + Endpoint Verification puts one vendor at the identity perimeter, the device-posture signal, the browser, the endpoint policy, and the apps behind it. An outage, breaking API change, pricing shift, or regulatory action against any one product propagates simultaneously. Multi-vendor zero-trust (Okta plus Crowdstrike plus Cloudflare Access plus Netskope) is heavier and more expensive, and the trust roots are diverse. The fact that Google's stack is cheaper and tighter is itself the lock-in. The strategic question is not technical; it is whether the CIO has accepted Google as a single trust root.
Zero-trust by the NIST definition. NIST 800-207 [9] defines zero-trust as continuous verification, dynamic policy, per-request authorization, microsegmentation, and explicit assumption of breach. IAP authenticates at the edge. It does not continuously re-verify long-lived sessions, segment the workload, or assume the post-auth blast radius is compromised. IAP is one piece of a zero-trust architecture, not the architecture. Calling an IAP rollout "zero-trust" without microsegmentation, workload identity, blast-radius controls, and independent observability is marketing.
Independent visibility and incident response. IAP authentication decisions live in Google's audit logs. Most enterprises do not replicate those decisions into their own SIEM with sufficient fidelity to reconstruct an incident independently. When IAP misbehaves, debugging happens inside Google's stack with Google's logs, on Google's support timeline. For regulated workloads with strict data sovereignty or independent-audit-trail requirements, this is a question worth asking explicitly, not assuming away.
The enterprise frame this leaves: IAP is the strongest edge-authentication primitive Google offers, and a substantial improvement over corporate VPN. Three questions matter before calling a deployment zero-trust. Is it actually identity plus context, or just identity? Has the organization accepted Google as a single trust root for identity, device, browser, and policy? Does the Context-Aware Access policy still match the threat model it was written for, or has it drifted? Personal stacks can answer those questions with one operator and a security key. Enterprise stacks cannot, and the answers there determine whether the deployment earns the label.
Full Deployment Pattern
The following checklist is the complete single-user deployment pattern I use. The first five components are baseline controls. The webhook and CORS entries are conditional: include them only when the app exposes third-party callbacks or browser cross-origin calls. Table 2 lists each component, its configuration, and its role.
Table 2. Component configuration for the IAP on Cloud Run single-user deployment pattern.
Baseline (every deployment):
| Component | Configuration | Purpose |
|---|---|---|
| Cloud Run ingress | --ingress=all or internal-and-cloud-load-balancing |
Controls which networks can reach the service |
| IAP | --iap --no-allow-unauthenticated |
Google-backed authentication gate before container |
| IAP user binding | roles/iap.httpsResourceAccessor on one email, scoped to service |
Single authorized principal at minimum privilege |
| Service account | Dedicated, least-privilege service account | Limits blast radius of SSRF and credential theft |
| Signed header validation | App validates X-Goog-IAP-JWT-Assertion |
Defense-in-depth if IAP is bypassed |
Conditional (only when the app needs them):
| Component | Configuration | Purpose | When to include |
|---|---|---|---|
| Webhook receptor | Separate unauthenticated service with HMAC validation, or OIDC-authenticated GCP-native caller | Handles external webhook providers that cannot use OIDC | App exposes third-party callbacks |
| CORS preflight | allow_http_options: true plus app handles OPTIONS |
Browser-compatible cross-origin support | App serves cross-origin browser calls |
This pattern applies consistently across the personal apps the author runs on GCP. The authentication layer lives entirely outside application code. No session management, no password hashing, and no token refresh logic needs to live inside the container.
When One Layer Is Not Enough
For most personal apps, the pattern above is sufficient. A small set of apps justify a second authentication tier in front of IAP. Not because IAP is weak on a green-field deployment, but because the threat model includes failure modes one layer cannot address.
Four failure modes drive the second layer:
- Google account compromise as single point of failure. Phishing-resistant MFA is strong but not absolute. An attacker who compromises the Google account (including session theft via a malicious browser extension or a social-engineered recovery flow) inherits access to every IAP-protected service simultaneously.
- Configuration drift.
--allow-unauthenticatedtoggled by mistake during debugging, a load balancer added without IAP, an IAM binding granted too broadly. A second enforcement point catches these without depending on continuous configuration audit. - IAP control-plane events. Outages, API behavior changes, or maintenance windows that change how IAP enforces policy. A second layer operated by a different vendor with different incident timelines reduces the blast radius of any single control-plane event.
- Independent audit trail. All IAP authentication decisions live in Google's audit logs. A second layer writes to a separate audit destination, which matters when the question is what happened and you need a record outside the vendor whose product is under investigation.
Cloudflare Access is the natural second layer: an independent enforcement point at the edge, a separate policy surface, and an independent audit log. It can also use a different identity provider if the goal is to reduce Google as the single trust root. The operational cost is real - two consent flows on first session, two policy surfaces to maintain, and added latency on cold sessions. For most personal apps this friction is not worth it. For the small set holding the highest-sensitivity material, the extra layer earns its keep.
The configuration, threat-model walkthrough, and operational tradeoffs are their own piece. A piece on layered edge authentication with Cloudflare Access in front of IAP is in the queue, undated; the Cloudflare side of the stack is in Putting Cloud Run Behind Cloudflare Pro.
See also:
- Explore the Site Data - the stack underneath jon.rehagen.net, including IAP at Cloud Run as the identity layer for private apps.
- GCP Sprawl Pt 1: Inventory - what happens when personal projects accumulate without governance.
- Why I Run Production-Grade Personal Infrastructure - the case for treating personal infra as a discipline.
References
All primary sources. Third-party blog posts and secondary explainers were excluded.
- IAP integration with Cloud Run - Google Cloud Blog - GA announcement, March 13, 2026.
- Chrome Enterprise Premium overview - Google Cloud Documentation - Paid features layered on IAP: device attributes, custom access levels, customization, non-Google Cloud resources.
- Enable IAP for Cloud Run - Identity-Aware Proxy Documentation - OAuth consent screen, same-organization constraint, load-balancer layering constraint.
- Configure IAP for Cloud Run - Cloud Run Documentation - Primary configuration reference; documents the IAP-runs-before-IAM ordering.
- Securing your app with signed headers - Identity-Aware Proxy - JWT validation and the
X-Goog-IAP-JWT-Assertionheader spec. - BeyondCorp: How Google Ditched VPNs - Google Cloud Blog - BeyondCorp origin, zero-trust access model background.
- CORSLeak: Abusing IAP for a Stealthy Data Exfiltration Attack - Mitiga - Research on CORS misconfiguration enabling data exfiltration through IAP.
- Introduction to service identity - Cloud Run - Least-privilege service account configuration for Cloud Run services.
- NIST SP 800-207: Zero Trust Architecture - The reference definition: continuous verification, dynamic policy, per-request authorization, microsegmentation, and explicit assumption of breach.
- How to use 1-click Identity Aware Proxy (IAP) with Cloud Run - Google Codelabs - Working example of the
--iapflag forgcloud run deploy.