Determine this is the right repository
Summary of the issue
Description
In google.auth.transport.requests.AuthorizedSession and google.auth.transport.urllib3.AuthorizedHttp, receiving a 401 Unauthorized triggers an automatic credential refresh / mTLS channel reconfiguration, followed by a recursive request retry (self.request(...) / self.urlopen(...)).
If the request payload (data or body) is a streaming payload (e.g., a file-like object, generator, or iterator), the initial request consumes the stream. The recursive retry then blindly attempts to send the exact same exhausted stream instance. This results in transmitting a 0-byte body (Content-Length: 0) to the server on the retried attempt.
For streaming payloads, the transport should likely not attempt an automatic retry and instead return the 401 response to the caller so they can safely reconstruct the stream and retry on their end. See HLD: go/sdk-mds-bound-token
Note on implementation: If the fix involves short-circuiting the auto-retry for streaming payloads, it is critical that credential refresh and mTLS channel reconfiguration still execute prior to returning the 401. If the logic returns early before running self.credentials.refresh() and self.configure_mtls_channel(), the internal session state remains stale. When the caller reconstructs the stream and issues a manual retry, it will immediately fail again with the exact same expired token or old mTLS certificate.
Affected Files
packages/google-auth/google/auth/transport/requests.py (AuthorizedSession.request)
packages/google-auth/google/auth/transport/urllib3.py (AuthorizedHttp.urlopen)
Determine this is the right repository
Summary of the issue
Description
In
google.auth.transport.requests.AuthorizedSessionandgoogle.auth.transport.urllib3.AuthorizedHttp, receiving a401 Unauthorizedtriggers an automatic credential refresh / mTLS channel reconfiguration, followed by a recursive request retry (self.request(...)/self.urlopen(...)).If the request payload (
dataorbody) is a streaming payload (e.g., a file-like object, generator, or iterator), the initial request consumes the stream. The recursive retry then blindly attempts to send the exact same exhausted stream instance. This results in transmitting a 0-byte body (Content-Length: 0) to the server on the retried attempt.For streaming payloads, the transport should likely not attempt an automatic retry and instead return the
401response to the caller so they can safely reconstruct the stream and retry on their end. See HLD: go/sdk-mds-bound-tokenNote on implementation: If the fix involves short-circuiting the auto-retry for streaming payloads, it is critical that credential refresh and mTLS channel reconfiguration still execute prior to returning the 401. If the logic returns early before running
self.credentials.refresh()andself.configure_mtls_channel(), the internal session state remains stale. When the caller reconstructs the stream and issues a manual retry, it will immediately fail again with the exact same expired token or old mTLS certificate.Affected Files
packages/google-auth/google/auth/transport/requests.py(AuthorizedSession.request)packages/google-auth/google/auth/transport/urllib3.py(AuthorizedHttp.urlopen)