Environment details
- API: auth, workload identity federation (
AwsCredentials, STS token exchange during Cloud Storage calls)
- OS type and version: Linux (AWS Lambda and AWS ECS)
- Java version: 25
- Version(s):
google-auth-library-oauth2-http 1.50.0 (libraries-bom 26.86.0), google-http-client 2.2.0. createFromHttpResponseException is unchanged in 1.53.0 and on main (05a998f).
Steps to reproduce
- Deterministic, no credentials needed: call
OAuthException.createFromHttpResponseException with an HttpResponseException whose content is a Google API error body (snippet below). It throws ClassCastException.
- Against the real STS: an
AwsCredentials whose token_url is https://sts.googleapis.com/v1/introspect gets HTTP 400 with {"error":{"code":400,"message":"Invalid JSON payload received. ...","status":"INVALID_ARGUMENT"}} and fails with the same ClassCastException. The misconfigured URL is only a way to get that body shape on demand; in production the same exception comes from https://sts.googleapis.com/v1/token.
Code example
package com.google.auth.oauth2;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpResponseException;
class OAuthExceptionNestedErrorRepro {
public static void main(String[] args) throws Exception {
HttpResponseException e =
new HttpResponseException.Builder(400, "Bad Request", new HttpHeaders())
.setContent(
"{\"error\":{\"code\":400,\"message\":\"Request contains an invalid argument.\",\"status\":\"INVALID_ARGUMENT\"}}")
.build();
OAuthException.createFromHttpResponseException(e); // java.lang.ClassCastException
}
}
Stack trace
Production, workload identity federation from AWS, token exchange triggered by a Cloud Storage request:
java.lang.ClassCastException: class com.google.api.client.util.ArrayMap cannot be cast to class java.lang.String
at com.google.auth.oauth2.OAuthException.createFromHttpResponseException(OAuthException.java:90)
at com.google.auth.oauth2.StsRequestHandler.exchangeToken(StsRequestHandler.java:129)
at com.google.auth.oauth2.ExternalAccountCredentials.exchangeExternalCredentialForAccessToken(ExternalAccountCredentials.java:567)
at com.google.auth.oauth2.AwsCredentials.refreshAccessToken(AwsCredentials.java:132)
at com.google.auth.oauth2.OAuth2Credentials.getRequestMetadata(OAuth2Credentials.java:189)
External references such as API reference guides
Any additional information below
OAuthException.java:90 casts error to String. The request-validation errors we could trigger on /v1/token come back in the OAuth form ({"error":"invalid_request","error_description":"..."}), but some production responses from /v1/token carry error as an object (Google's API error format), which makes the cast throw. We could not capture those bodies because the library drops them.
- The
ClassCastException is thrown from inside catch (HttpResponseException e) in StsRequestHandler.exchangeToken. It is a RuntimeException, so it escapes the IOException contract of refresh() / getRequestMetadata(), and it has no cause, so the HttpResponseException holding the status and body is lost. LoggingUtils logs the response only on success, so the body is not logged either. When this happened to us intermittently in production (a burst of failures across several independent GCP projects within minutes), we could not tell whether STS was rate limiting, unavailable, or refusing the credential.
- Because the
ClassCastException is not an IOException, google-cloud-storage classifies it as unretryable (Unretryable error (attempts: 1, maxAttempts: 6)), so a transient STS failure is not retried either.
- The same method is also called from
ExternalAccountAuthorizedUserCredentials.java:204.
- Related: a body without an
error key makes the constructor's checkNotNull(errorCode) throw NullPointerException.
Environment details
AwsCredentials, STS token exchange during Cloud Storage calls)google-auth-library-oauth2-http1.50.0 (libraries-bom 26.86.0),google-http-client2.2.0.createFromHttpResponseExceptionis unchanged in 1.53.0 and onmain(05a998f).Steps to reproduce
OAuthException.createFromHttpResponseExceptionwith anHttpResponseExceptionwhose content is a Google API error body (snippet below). It throwsClassCastException.AwsCredentialswhosetoken_urlishttps://sts.googleapis.com/v1/introspectgets HTTP 400 with{"error":{"code":400,"message":"Invalid JSON payload received. ...","status":"INVALID_ARGUMENT"}}and fails with the sameClassCastException. The misconfigured URL is only a way to get that body shape on demand; in production the same exception comes fromhttps://sts.googleapis.com/v1/token.Code example
Stack trace
Production, workload identity federation from AWS, token exchange triggered by a Cloud Storage request:
External references such as API reference guides
com.google.api.client.util.ArrayMapcannot be cast to classjava.lang.Stringbazelbuild/bazel#29600: the same exception fromIdentityPoolCredentials(GitLab CI, workload identity federation), closed as not reproducible.createFromHttpResponseException, but targets theoauth2-bound-tokensbranch and does not mention this bug.Any additional information below
OAuthException.java:90castserrortoString. The request-validation errors we could trigger on/v1/tokencome back in the OAuth form ({"error":"invalid_request","error_description":"..."}), but some production responses from/v1/tokencarryerroras an object (Google's API error format), which makes the cast throw. We could not capture those bodies because the library drops them.ClassCastExceptionis thrown from insidecatch (HttpResponseException e)inStsRequestHandler.exchangeToken. It is aRuntimeException, so it escapes theIOExceptioncontract ofrefresh()/getRequestMetadata(), and it has no cause, so theHttpResponseExceptionholding the status and body is lost.LoggingUtilslogs the response only on success, so the body is not logged either. When this happened to us intermittently in production (a burst of failures across several independent GCP projects within minutes), we could not tell whether STS was rate limiting, unavailable, or refusing the credential.ClassCastExceptionis not anIOException, google-cloud-storage classifies it as unretryable (Unretryable error (attempts: 1, maxAttempts: 6)), so a transient STS failure is not retried either.ExternalAccountAuthorizedUserCredentials.java:204.errorkey makes the constructor'scheckNotNull(errorCode)throwNullPointerException.