Repository: googleapis/google-auth-library-python

Issue:
Token refresh on each API request when using UserAccessTokenCredentials
I observed that requests to Google Cloud APIs take much longer when using `UserAccessTokenCredentials` (documented [here](https://google-auth.readthedocs.io/en/stable/reference/google.oauth2._credentials_async.html#google.oauth2._credentials_async.UserAccessTokenCredentials)) as `credentials` for client libraries, e.g., Storage [library](https://github.com/googleapis/python-storage) for Python.

Diving into the code, I realized that `UserAccessTokenCredentials` triggers token refresh (resulting in an additional API request) on each request due to how the `before_request` method is [implemented](https://github.com/googleapis/google-auth-library-python/blob/v2.21.0/google/oauth2/credentials.py#L530):

```python
def before_request(self, request, method, url, headers):
    self.refresh(request)
    self.apply(headers)
```

In [comparison](https://github.com/googleapis/google-auth-library-python/blob/abedaa81f8f19fc34fad5687a0205cd7f71cd4fd/google/auth/credentials.py#L133), the `before_request` method in the base class has a mechanism to trigger token refresh only when necessary:

```python
if not self.valid:
    self.refresh(request)
```

Would it be reasonable to check token validity before request in the `UserAccessTokenCredentials` strategy?





Discussion:
I think `google.oauth2.credentials.Credentials` should be used instead. (In other words, you run `gcloud auth application-default login`, and let the client lib pick up the application default credentials on its own).

`google.oauth2.credentials.UserAccessTokenCredentials` simply runs `gcloud auth print-access-token` to get the token, this class doesn't even cache the token. To check the validity requires sending the token to some token info endpoint, simply checking `self.valid` won't work. I wouldn't recommend using this credential.  `google.oauth2.credentials.Credentials` is the one I recommend.



Resolving patch:
diff --git a/google/oauth2/credentials.py b/google/oauth2/credentials.py
--- a/google/oauth2/credentials.py
+++ b/google/oauth2/credentials.py
@@ -35,6 +35,7 @@
 import io
 import json
 import logging
+import warnings
 
 import six
 
@@ -498,6 +499,13 @@ class UserAccessTokenCredentials(credentials.CredentialsWithQuotaProject):
     """
 
     def __init__(self, account=None, quota_project_id=None):
+        warnings.warn(
+            "UserAccessTokenCredentials is deprecated, please use "
+            "google.oauth2.credentials.Credentials instead. To use "
+            "that credential type, simply run "
+            "`gcloud auth application-default login` and let the "
+            "client libraries pick up the application default credentials."
+        )
         super(UserAccessTokenCredentials, self).__init__()
         self._account = account
         self._quota_project_id = quota_project_id


Test patch:
diff --git a/system_tests/secrets.tar.enc b/system_tests/secrets.tar.enc
Binary files a/system_tests/secrets.tar.enc and b/system_tests/secrets.tar.enc differ
diff --git a/tests/oauth2/test_credentials.py b/tests/oauth2/test_credentials.py
--- a/tests/oauth2/test_credentials.py
+++ b/tests/oauth2/test_credentials.py
@@ -951,25 +951,34 @@ def test_unpickle_old_credentials_pickle(self):
 
 class TestUserAccessTokenCredentials(object):
     def test_instance(self):
-        cred = credentials.UserAccessTokenCredentials()
-        assert cred._account is None
+        with pytest.warns(
+            UserWarning, match="UserAccessTokenCredentials is deprecated"
+        ):
+            cred = credentials.UserAccessTokenCredentials()
+            assert cred._account is None
 
-        cred = cred.with_account("account")
-        assert cred._account == "account"
+            cred = cred.with_account("account")
+            assert cred._account == "account"
 
     @mock.patch("google.auth._cloud_sdk.get_auth_access_token", autospec=True)
     def test_refresh(self, get_auth_access_token):
-        get_auth_access_token.return_value = "access_token"
-        cred = credentials.UserAccessTokenCredentials()
-        cred.refresh(None)
-        assert cred.token == "access_token"
+        with pytest.warns(
+            UserWarning, match="UserAccessTokenCredentials is deprecated"
+        ):
+            get_auth_access_token.return_value = "access_token"
+            cred = credentials.UserAccessTokenCredentials()
+            cred.refresh(None)
+            assert cred.token == "access_token"
 
     def test_with_quota_project(self):
-        cred = credentials.UserAccessTokenCredentials()
-        quota_project_cred = cred.with_quota_project("project-foo")
+        with pytest.warns(
+            UserWarning, match="UserAccessTokenCredentials is deprecated"
+        ):
+            cred = credentials.UserAccessTokenCredentials()
+            quota_project_cred = cred.with_quota_project("project-foo")
 
-        assert quota_project_cred._quota_project_id == "project-foo"
-        assert quota_project_cred._account == cred._account
+            assert quota_project_cred._quota_project_id == "project-foo"
+            assert quota_project_cred._account == cred._account
 
     @mock.patch(
         "google.oauth2.credentials.UserAccessTokenCredentials.apply", autospec=True
@@ -978,7 +987,10 @@ def test_with_quota_project(self):
         "google.oauth2.credentials.UserAccessTokenCredentials.refresh", autospec=True
     )
     def test_before_request(self, refresh, apply):
-        cred = credentials.UserAccessTokenCredentials()
-        cred.before_request(mock.Mock(), "GET", "https://example.com", {})
-        refresh.assert_called()
-        apply.assert_called()
+        with pytest.warns(
+            UserWarning, match="UserAccessTokenCredentials is deprecated"
+        ):
+            cred = credentials.UserAccessTokenCredentials()
+            cred.before_request(mock.Mock(), "GET", "https://example.com", {})
+            refresh.assert_called()
+            apply.assert_called()