Google Cloud Workload Identity Federation OIDC Integration

eSummary: This document explains, step by step, how to use MonoSign as an OIDC identity provider so that workloads (pipelines, services, scripts) can securely access Google Cloud resources without any static GCP service account key file, using Google Cloud Workload Identity Federation (WIF).

1. Overview

MonoSign acts as the OIDC identity provider that Google Cloud trusts via Workload Identity Federation. A workload authenticates with its own MonoSign client credentials and gets a short-lived Google Cloud token — no service account key file involved. The rest of this page is the exact setup to get that working.

2. Architecture

The diagram below shows two different teams (Team A, Team B) sharing the same MonoSign issuer and the same Google Cloud Workload Identity Pool/Provider, while remaining fully isolated from each other via different service accounts:

resim-20260901-204100.png


Flow summary:

  1. The workload calls MonoSign with its own client_id + client_secret using the client_credentials grant type.

  2. MonoSign returns a signed OIDC JWT (access_token) representing the workload's identity. The token's sub claim is that client's client_id, and its aud claim is the Google Cloud provider's canonical resource name.

  3. The workload sends this JWT to Google STS (sts.googleapis.com/v1/token) and receives a Google "federated token" in exchange.

  4. The federated token is used to impersonate the target service account (iamcredentials.googleapis.com ... :generateAccessToken).

  5. The resulting service account access token is used to make real Google Cloud API calls.

3. Prerequisites

  • Access to IAM & Admin → Workload Identity Federation in the Google Cloud project

  • Permission to create new Applications/Clients in MonoSign

  • gcloud CLI installed locally (install with brew install --cask google-cloud-sdk) — needed for testing and verification steps

4. Google Cloud Setup

4.1 Create a Workload Identity Pool

IAM & Admin → Workload Identity Federation → Get started → enter a Pool ID/Name (e.g. monofor).

resim-20260903-154058.png
resim-20260903-160235.png


4.2 Add an OIDC Provider

Select OpenID Connect (OIDC) as the provider type.

Field

Value

Provider name / ID

monofor

Issuer (URL)

https://id.yourdomain.com/ (MonoSign's Configuration Url with the /.well-known/openid-configuration suffix removed — including the trailing /, it must exactly match MonoSign's actual iss claim value)

Audiences

Leave Default audience selected (see Section 9 — changing this caused a serious issue, details in Troubleshooting)

resim-20260903-160347.png
resim-20260903-160711.png

Click for next step, “Continue” button.

4.3 Attribute Mapping

On the "Configure provider attributes" screen, the required mapping is:

google.subject = assertion.sub

This maps the sub claim from the MonoSign token (in the client_credentials flow, this value equals the client_id) to the google.subject field that Google IAM recognizes.

Caution: When entering this manually, it must be exactly assertion.sub. Due to copy-paste issues, if only sub ends up in the field, you'll get "Invalid attribute mapping. undeclared reference to 'sub'".

resim-20260903-160900.png


5. MonoSign Setup

5.1 Create a New Application (OIDC/OpenID)

In the MonoSign admin panel, create a separate Application for each workload/team (Type: OIDC/OpenID).

clipboard-2026-09-01-234448-0E6F80A8-20260901-204448.png
clipboard-2026-09-01-235027-1C0280E0-20260901-205027.png
resim-20260901-205418.png
resim-20260901-205521.png
resim-20260901-205747.png
resim-20260901-205826.png
resim-20260902-061208.png
resim-20260902-061325.png


5.2 Enable the Client Credentials (M2M) Flow

By default, MonoSign applications use the Authorization Code grant type — this requires an interactive human login via browser and is not suitable for an automated, unattended workload scenario.

Under Edit → OIDC/OpenID Settings → Configuration:

Field

Value

Why

Public Client

No

When Public Client is on, no client_secret is required, PKCE becomes mandatory, and the client_credentials grant is rejected. For an M2M workload scenario, Public Client must be off.

Allowed Redirect URIs

Can be left empty

The client_credentials flow never uses a browser/redirect

resim-20260903-064239.png


5.3 Set the Audience Claim

In the same Configuration tab, set the Audience field to the Google Cloud provider's canonical resource name:

https://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID
resim-20260903-064539.png

Caution: This must be entered exactly in this format, including the https:// prefix. If left empty, the issued token will have no aud claim at all, and the Google STS token exchange will fail with "invalid_grant".

6. Grant Access

Authenticating the workload isn't enough — Google Cloud also needs to know what it's allowed to do (authorization). This is done in two steps:

6.1 Create a Service Account

IAM & Admin → Service Accounts → Create Service Account. A dedicated service account per workload/team is recommended (avoid shared/general-purpose SAs).

6.2 Grant a Role to the Service Account

Grant the SA the narrowest possible role(s) it actually needs (least privilege). Example: if it only needs to read project metadata, roles/browser; for a narrower need, create a custom role:

Bash
gcloud iam roles create saListOnly \
  --project=PROJECT_ID \
  --title="SA List Only" \
  --permissions="iam.serviceAccounts.list" \
  --stage=GA

6.3 Bind the Federated Identity to the Service Account

From the service account's own Permissions → Grant Access screen (not the project's IAM — the SA's own resource-level IAM policy), map the MonoSign client's sub value to permission to impersonate this SA:

Principal: principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/monofor/subject/CLIENT_ID
Role: Workload Identity User (roles/iam.workloadIdentityUser)

IAM policy changes can take a few minutes to propagate globally ("eventual consistency"). If the first tests right after a change give unexpected results, wait a few minutes and retry.

7. Testing

End-to-end verification consists of 4 steps:

7.1 Get a Token from MonoSign

Bash
curl -s -X POST https://id.yourdomain.com/openid/token \
  -d grant_type=client_credentials \
  -d client_id=CLIENT_ID \
  -d client_secret=CLIENT_SECRET

Note: do not send a scope=openid parameter together with client_credentials — the openid scope requires a "subject" (a user), whereas in client_credentials the subject is the client itself; this can cause an invalid_scope error. Send the request with no scope parameter at all.

7.2 Exchange It for a Federated Token via Google STS

Bash
curl -X POST https://sts.googleapis.com/v1/token \
  -H "Content-Type: application/json" \
  -d '{
    "audience": "//iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/monofor/providers/monofor",
    "grantType": "urn:ietf:params:oauth:grant-type:token-exchange",
    "requestedTokenType": "urn:ietf:params:oauth:token-type:access_token",
    "scope": "https://www.googleapis.com/auth/cloud-platform",
    "subjectTokenType": "urn:ietf:params:oauth:token-type:jwt",
    "subjectToken": "MONOSIGN_ACCESS_TOKEN"
  }'

Format warning: The audience field of this request must be in the //iam.googleapis.com/... format (no scheme / no https:). This is a different field from the https://iam.googleapis.com/... value we set in MonoSign's Audience field — the two should not be confused. Details in Section 9.

7.3 Impersonate the Service Account

Bash
curl -X POST "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/SA_EMAIL:generateAccessToken" \
  -H "Authorization: Bearer FEDERATED_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"scope": ["https://www.googleapis.com/auth/cloud-platform"]}'

7.4 Make a Real API Call

Bash
curl -H "Authorization: Bearer SA_ACCESS_TOKEN" \
  "https://cloudresourcemanager.googleapis.com/v1/projects/PROJECT_ID"

If this returns 200, the chain is working end to end.

8. Multi-Team / Multi-Client Pattern

Don't create a new Google Cloud provider per team. Instead:

  • Google Cloud side: keep a single Pool + single Provider.

  • MonoSign side: a separate Application/Client per team/workload (its own client_id + client_secret, its own sub value).

  • IAM binding: each client's sub value is bound to its own dedicated service account, with its own minimal role.

Client secrets should never be shared with people or used manually — they belong in that pipeline's own CI/CD secret store (GitHub Actions Secrets, GitLab CI Variables, Vault, etc.) and should be called automatically by the pipeline.

9. Troubleshooting

Error

Cause

Fix

Please enter an issuer URL

The Issuer field was left empty when creating the provider

Enter MonoSign's Configuration Url with the /.well-known/openid-configuration suffix removed

Invalid attribute mapping. undeclared reference to 'sub'

The attribute mapping field ended up with just sub instead of assertion.sub

Clear the field and type exactly assertion.sub

invalid_scope (on the MonoSign token request)

scope=openid was added to the client_credentials request

Don't send a scope parameter at all

Token has no aud claim at all

The Audience field is empty in the MonoSign application

Set Audience to the GCP provider's canonical resource name (https://iam.googleapis.com/...)

The audience in ID Token [...] does not match the expected audience

Format mismatch between the STS request's audience field (//iam.googleapis.com/...) and MonoSign's Audience claim (https://iam.googleapis.com/...), OR the provider's "Allowed audiences" was mistakenly pinned to one specific client_id

The STS request's audience must be //iam.googleapis.com/..., and MonoSign's Audience claim must be https://iam.googleapis.com/.... To check the provider's actual configuration: gcloud iam workload-identity-pools providers describe PROVIDER --location=global --workload-identity-pool=POOL --format=json — if allowedAudiences is populated with a specific client_id, clear it with --allowed-audiences="" to fall back to "Default audience"

Invalid value for "audience" (on the STS request)

An https://-prefixed value was sent in STS's audience field

This field must strictly be in the //iam.googleapis.com/... format (no scheme)

The user information request cannot be handled. Invalid Auth Request or Session.

MonoSign's /openid/userinfo endpoint was called with a client_credentials token

This endpoint only works for tokens tied to a user session (from the Authorization Code flow); it's not used in the client_credentials scenario

An operation unexpectedly succeeds despite lacking permission (e.g. an SA granted only iam.serviceAccountViewer can also read project info)

Many of Google's predefined roles implicitly bundle baseline permissions like resourcemanager.projects.get for console usability

For a truly narrow demo/isolation scenario, create a custom role containing only the single permission actually needed

Secret Manager API can't be enabled: FAILED_PRECONDITION: Billing account ... not found

No billing account is linked to the project

Continue with APIs that don't require billing (Cloud Resource Manager, IAM), or enable billing/free trial from the Console