fetch_id_token_credentials doesn't follow AIP-4110
AIP-4110 specifies where should client libraries load credentials from. It's correctly used by google.auth.default(), which loads the OAuth credentials (ie credentials with access token).
In our service, we need the same default credentials, but our use case requires OpenID credentials. google.oauth2.id_token.fetch_id_token_credentials() pretty much does this, as it also states in the doc string:
Create the ID Token credentials from the current environment
This function acquires ID token from the environment in the following order. See https://google.aip.dev/auth/4110.
However, there are notable differences in the OpenID flow:
- it doesn't support impersonated or external credentials
- it doesn't check "gcloud default credentials through its default path", as stated in the AIP
It would be good if fetch_id_token_credentials() would match google.auth.default().
We currently use a workaround:
- we get default OAuth credentials from
google.auth.default() - based on the type, we decide what OpenID credentials should be returned
The following code works, but it's not complete (doesn't support all kinds of credentials) and also does some redundant requests to metadata server.
def fixed_fetch_id_token_credentials(audience: str, request=None):
"""Get OpenID credentials from the current environment.
NOTE: This is needed only because google.oauth2.id_token.fetch_id_token_credentials doesn't support impersonated
credentials. Once it does, this function can be removed.
"""
creds, _project_id = google.auth.default()
if request is None:
request = google.auth.transport.requests.Request()
if isinstance(creds, google.auth.impersonated_credentials.Credentials):
id_creds = google.auth.impersonated_credentials.IDTokenCredentials(
creds, audience, include_email=True
)
elif isinstance(creds, google.oauth2.service_account.Credentials):
id_creds = google.oauth2.service_account.IDTokenCredentials(
signer=creds.signer,
service_account_email=creds.service_account_email,
token_uri=creds._token_uri,
quota_project_id=creds.quota_project_id,
target_audience=audience,
)
elif isinstance(creds, google.auth.compute_engine.credentials.Credentials):
id_creds = google.auth.compute_engine.credentials.IDTokenCredentials(
request,
audience,
use_metadata_identity_endpoint=True,
quota_project_id=creds.quota_project_id,
)
elif isinstance(creds, google.oauth2.credentials.Credentials):
raise ValueError(
"IDTokens are not supported for human accounts. Provide a service account instead."
)
else:
raise ValueError(f"Unknown credentials type {type(creds)}")
return id_creds
We'd appreciate the library supporting this natively so that we could delete the code.
Indeed! I just came across this difference as well, as in the Go library it's been implemented adherent to the AIP spec.