Secure Cloud Run With Auth0 and Workforce Identity Federation

Hosting in the cloud securely can feel daunting. Acronyms like JWT and OIDC blur together, and developers constantly worry: will someone steal my credentials? Will I wake up to a six-figure invoice from a faceless cloud provider? Fortunately, if you’re on Google Cloud, tools like Cloud Run, Identity-Aware Proxy (IAP), and Workforce Identity Federation make secure hosting both simple and affordable. In this post, we’ll walk through deploying a secure website on Cloud Run with Auth0 authentication, powered by IAP and Workforce Identity Federation: all within the free tier.

What is Workforce Identity Federation?

Workforce Identity Federation allows you to authenticate users to Google Cloud resources using your existing identity provider (IdP). Instead of managing separate Google credentials, users sign in through the IdP you already use. In our case, that IdP is Auth0.

What is Identity-Aware Proxy (IAP)?

Identity-Aware Proxy (IAP) acts as a gatekeeper in front of your applications and services on Google Cloud. Instead of leaving your app open to the internet, IAP ensures that only authenticated and authorized users can reach it. When someone tries to access your app, IAP checks their identity against your configured identity provider before letting them through. In this post, IAP will sit in front of our Cloud Run service to enforce authentication with Auth0.

Architecture Overview

In this example, I’ll show how I host a budgeting website that my wife and I can access securely to monitor our spending. Instead of creating separate Google Workspace accounts, we both log in through Auth0, which acts as our identity provider. Behind the scenes, Google Cloud services handle the rest:

Architecture Diagram

High level flow.

  1. Auth0: External identity provider where my wife and I have logins.
  2. Workforce Identity Federation: Maps our Auth0 identities into Google Cloud.
  3. Identity-Aware Proxy (IAP): Enforces that only authenticated users (us) can access the budgeting site.
  4. Cloud Run: Runs the containerised budgeting website.

Step 1: Deploy the website

The first step is to deploy a containerised website as a Cloud Run Service.

Budgeting Website

Simple budgeting web app to get insights into our spending.

In this post, we’ll be writing Terraform to provision the Google Cloud resources. This allows the resources to be destroyed at the end to keep things tidy.

The following Terraform creates a Cloud Run service running an image called budgeting from an Artifact Registry. The Cloud Run service is using Google’s new native IAP integration which means you don’t need your own load balancer. Apply it with terraform apply.

resource "google_cloud_run_v2_service" "budget_website" {
  provider     = google-beta # Required to use Cloud Run's native IAP integration
  name         = "budgeting"
  location     = "australia-southeast1"
  project      = "<PROJECT_ID>"
  ingress      = "INGRESS_TRAFFIC_ALL"
  launch_stage = "BETA" # Required to use Cloud Run's native IAP integration
  iap_enabled  = true # Required to use Cloud Run's native IAP integration

  template {
    service_account = google_service_account.service_account.email

    containers {
      image = "australia-southeast1-docker.pkg.dev/<PROJECT_ID>/<ARTIFACT-REGISTRY>/budgeting:latest"
    }
  }
}

resource "google_cloud_run_v2_service_iam_member" "iap_invoker" {
  location = google_cloud_run_v2_service.budget_website.location
  project  = google_cloud_run_v2_service.budget_website.project
  name     = google_cloud_run_v2_service.budget_website.name
  role     = "roles/run.invoker"
  member   = "serviceAccount:service-<PROJECT-NUMBER>@gcp-sa-iap.iam.gserviceaccount.com"
}

resource "google_service_account" "service_account" {
  account_id   = "budgeting"
  project      = "<PROJECT_ID>"
  display_name = "budgeting"
}

Once the Terraform applies, navigate to the Google Cloud Console to confirm the Cloud Run service is running and note the URL. It should look something like https://budgeting-<PROJECT-NUMBER>.australia-southeast1.run.app. This will be needed when configuring Auth0.

If you were to navigate to the new URL, IAP would send you to the Google login page (after which you’d get an IAP permission denied page as we haven’t granted access to the Cloud Run service yet). We want IAP to instead send you to Auth0 to login.

Step 2: Configure Auth0 as Identity Provider

To configure Auth0 to be an Identity Provider and map Auth0 users with Workforce Identity Federation, we need an Auth0 application.

Create an Application in Auth0

  1. Navigate to your Auth0 Dashboard
  2. Go to ApplicationsCreate Application
  3. Choose Single Page Web Application
  4. Name it something like “Budgeting”
Create Auth0 Application

Create Auth0 Application.

  1. Once your Auth0 application is created, under the settings tab you’ll find a domain, a client ID, and a client secret. Note all three of these for later.
Note the domain, client ID, and client secret

Note the domain, client ID, and client secret.

  1. Using your Cloud Run URL from earlier, add this to the Application Login URI and the Allowed Web Origins. The Allowed Callback URLs we will populate once we create the Workforce Identity Federation pool and provider in the next section.
Update login and allowed origins

Update Login URL and Allowed Origins.

So far you have a website hosted as a Cloud Run service protected using Cloud Run’s native IAP integration and an Auth0 application. The next step is to link the two so that IAP makes your users sign in with Auth0.

Step 3: Create Workforce Identity Pool and provider

The following Terraform creates a Workforce Identity Federation pool called wallace-auth0 (name yours appropriately!) and a provider called auth0-oidc-provider. This provider is configured with your Auth0 domain as the issuer URI (so Google can fetch Auth0’s public keys to validate tokens), and the client ID and client secret for your Auth0 application. We need the client ID and client secret because we’re using the OIDC authorization code flow (response_type = "CODE").

With code flow, Google receives an authorisation code from Auth0 after your user signs in, then exchanges it for tokens directly with Auth0 in a backend call. That’s why Google needs the secret: it has to authenticate itself when making that exchange.

If you were to configure implicit flow instead, Auth0 would return the ID token directly in the browser redirect, meaning no client secret is needed. However, implicit flow is considered less secure (tokens pass through the browser) and limited by URL length, so code flow is strongly recommended for production use.

resource "google_iam_workforce_pool" "auth0_pool" {
  workforce_pool_id = "wallace-auth0"
  display_name      = "wallace-auth0"
  parent            = "organizations/<ORG_ID>"
  location          = "global"
}

resource "google_iam_workforce_pool_provider" "auth0_provider" {
  workforce_pool_id = google_iam_workforce_pool.auth0_pool.workforce_pool_id
  location          = google_iam_workforce_pool.auth0_pool.location
  provider_id       = "auth0-oidc-provider"
  display_name      = "auth0-oidc-provider"
  attribute_mapping = {
    "google.subject" = "assertion.sub"
  }
  oidc {
    issuer_uri = "https://<AUTH0_DOMAIN>"
    client_id  = "<AUTH0_CLIENT_ID>"
    client_secret {
      value {
        plain_text = "<AUTH0_CLIENT_SECRET>"
      }
    }
    web_sso_config {
      response_type             = "CODE"
      assertion_claims_behavior = "MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS"
    }
  }
}

The other point to note is the attribute mapping. This tells Google which claim (a piece of information about the user that is packaged inside the token) from the Auth0 ID token should be used as the unique identity for the user. In the example:

attribute_mapping = {
  "google.subject" = "assertion.sub"
}
  • assertion.sub is the standard OIDC subject claim (a unique identifier for the user in Auth0).
  • google.subject is the required claim in Google’s side that Workforce Identity Federation uses to establish the Google principal.

You can also map additional claims such as email or groups if you want additional access controls (you almost certainly would want to check the user was in a specific group in an enterprise setting for example), but in my example my Auth0 tenant only has two users.

Now that we have our Workforce Identity Federation pool and provider, go back to your Auth0 application and populate the Allowed Callback URLs with https://auth.cloud.google/signin-callback/locations/global/workforcePools/wallace-auth0/providers/auth0-oidc-provider (alter with the name of your pool and provider).

Auth0 callback URLs

Enter your Workforce Identity Provider name callback URL

At this point to prove Workforce Identity Federation is setup correctly, you should be able to sign into the federated Google Cloud Console. Following that link you should see a page prompting you for your Workforce Identity Federation provider name, in our case locations/global/workforcePools/wallace-auth0/providers/auth0-oidc-provider.

Federated Google Cloud Console

Enter your Workforce Identity Provider name.

Entering your provider name, you’ll be directed to the Auth0 login page.

Auth0 login screen

Login with Auth0.

Finally, after entering your Auth0 login details, you’ll land at a special stripped down version of the Cloud Console for Workforce Identity Federated users.

Federated Google Cloud Console

The Federated Google Cloud Console supports fewer services than normal.

If all of this works, congratulations you’ve setup Workforce Identity Federation with Auth0 correctly! The final step is to configure IAP to use it to authenticate and authorise users trying to access your website.

Step 3: Configure IAP to use Auth0

To configure IAP to use the Workforce Identity Federation pool we need to create an IAM oauth client and client secret. NOTE: this oauth client is distinct from an IAP oauth client (the naming is very confusing). We then need to configure IAP to use the Workforce Identity Federation pool and the oauth client and secret.

The Terraform below creates these resources.

resource "google_iam_oauth_client" "oauth_client" {
  oauth_client_id       = "iap-wfif"
  location              = "global"
  project               = "<PROJECT_ID>"
  allowed_grant_types   = ["AUTHORIZATION_CODE_GRANT"]
  # Another confusing point is the client ID in the allowed redirect URIs you don't have 
  # until AFTER the client is created. See below for how to navigate this. 
  allowed_redirect_uris = ["https://iap.googleapis.com/v1/oauth/clientIds/ad118cb87-d2c4-478b-9d18-2a4fed26ada9:handleRedirect"]
  allowed_scopes        = ["https://www.googleapis.com/auth/cloud-platform"]
  client_type           = "CONFIDENTIAL_CLIENT"
  display_name          = "iap-wfif"
  description           = "An application registration for iap-wfif"
}

resource "google_iam_oauth_client_credential" "auth0-wif-secret" {
  oauthclient                = google_iam_oauth_client.oauth_client.oauth_client_id
  location                   = google_iam_oauth_client.oauth_client.location
  oauth_client_credential_id = "iap-wfif-secret"
  disabled                   = false
  display_name               = "iap-wfif client credential"
}

resource "google_iap_settings" "iap_settings" {
  name = "projects/<PROJECT_ID>"

  access_settings {
    workforce_identity_settings {
      workforce_pools = [google_iam_workforce_pool.auth0_pool.id]
      oauth2 {
        client_id     = google_iam_oauth_client.oauth_client.client_id
        client_secret = google_iam_oauth_client_credential.auth0-wif-secret.client_secret
      }
    }
  }
}

The one tricky bit in this Terraform snippet is you don’t have the client ID until after the client is created, so initially you need to create it with allowed_redirect_uris = []. Once created you can find the client ID with the following gcloud command.

gcloud iam oauth-clients describe iap-wfif --project <PROJECT_ID> --location global

Once applied, when navigating to your Cloud Run website you should be greeted with the Auth0 login page. However, after signing in IAP will send you to the following page:

Access denied page

IAP says no.

The final step is to grant the Workforce Identity Federation pool access to IAP.

resource "google_project_iam_member" "iap_access" {
  project = "<PROJECT_ID>"
  role    = "roles/iap.httpsResourceAccessor"
  member  = "principalSet://iam.googleapis.com/${google_iam_workforce_pool.auth0_pool.id}/*"
}

For simplicity’s sake we’re binding this at the project level (we only have one Cloud Run service) and to the entire Workforce Identity Federation pool (we only have two users), but in a real environment you would likely bind this to the Cloud Run service and add attribute conditions such as enforcing the user belongs to a specific group.

Once that binding has been given 5-10 minutes to propagate, you should finally be allowed through to the website!

Conclusion

In this post, we walked through setting up Workforce Identity Federation with Auth0 and IAP using Cloud Run’s native integration. Security can feel daunting, but by relying on Google services like IAP and Workforce Identity Federation, you can create a smooth, secure sign-in flow for your Cloud Run-hosted websites, all without spending a cent!


Have questions about Google Cloud? Feel free to reach out through the contact page or connect with me on social media.