The OIDC token has no power in GCP on its own. The runner posts it to Google's Security Token Service, which verifies it against Forgejo's public keys and returns a short-lived federated access token: GCP now recognizes the identity, but this is not the service account's token yet.
Call sequence
curl -X POST https://sts.googleapis.com/v1/token \ -H 'Content-Type: application/json' \ -d '{ "audience": "//iam.googleapis.com/projects/1234567890/locations/global/workloadIdentityPools/lab-pool/providers/ci-provider", "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": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjlZUDA2bjJvMTd6c3NZMWVBRkFoOUhoYjdTUnhqbGdhWmNmLUdiN2xmaU0iLCJ0eXAiOiJKV1QifQ.eyJhY3RvciI6ImF1dGhsYWIiLCJhdWQiOiIvL2lhbS5nb29nbGVhcGlzLmNvbS9wcm9qZWN0cy8xMjM0NTY3ODkwL2xvY2F0aW9ucy9nbG9iYWwvd29ya2xvYWRJZGVudGl0eVBvb2xzL2xhYi1wb29sL3Byb3ZpZGVycy9jaS1wcm92aWRlciIsImJhc2VfcmVmIjoiIiwiZXZlbnRfbmFtZSI6….anonymized-signature-not-valid-for-cryptographic-use" }'
curl https://forgejo.authlab.app/api/actions/.well-known/openid-configuration
{ "issuer": "https://forgejo.authlab.app/api/actions", "jwks_uri": "https://forgejo.authlab.app/api/actions/.well-known/keys", "subject_types_supported": ["public"], "response_types_supported": ["id_token"], "id_token_signing_alg_values_supported": ["RS256"], "scopes_supported": ["openid"], "claims_supported": ["sub", "aud", "exp", "iat", "iss", "nbf", "actor", "base_ref", "event_name", "head_ref", "ref", "ref_protected", "ref_type", "repository", "repository_owner", "run_attempt", "run_id", "run_number", "sha", "workflow", "workflow_ref"] }
curl https://forgejo.authlab.app/api/actions/.well-known/keys
{ "keys": [ { "alg": "RS256", "e": "AQAB", "kid": "9YP06n2o17zssY1eAFAh9Hhb7SRxjlgaZcf-Gb7lfiM", "kty": "RSA", "n": "vv9BnIrR-ZzrN7rQZwM6bIeGf_6jV-B8B_PDlTPofZXHrfWbfpoEJcTVEX5aR8YZrfEbDDXxYwbhecJl1x1CUeCtP_8f8S65XpFcj6yJBQ0AQoWjoZp_IB-XLuvT5i6MMxyCZVd07os51ovsKd1yt_uL6YBunifBzBEIE_lNg45bBd2shBEAyCTJAk2GBqDoIm4vDmSGv3Bq1IZ4acz1Yqxlu0gy29FPazy1HcLzVKPYsHc8KyxdsusFSmCesPW8pLOjzYchKhKSKSpxAZZdls2GHIP7Wn_ZKMM7UbmJoeIDakhEVJ8sJtqsKQr6L6uRiqrAh9SXd28MnwPx3_JEUw", "use": "sig" } ] }
{ "access_token": "ya29.c.REDACTED-federated-access-token-not-valid-for-use", "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", "token_type": "Bearer", "expires_in": 3599 }
What STS validates
STS checks the OIDC token before it mints anything. Hover a claim for why.
audience request.audience ≡ token.aud
signature token.sig ✓ issuer JWKS