fix: rename CLOCK_SKEW and separate client/server user case (#863)

* fix: rename CLOCK_SKEW and separate client/server user case

* update clock skew to 20s
diff --git a/google/auth/_helpers.py b/google/auth/_helpers.py
index 11c6b1a..55adf5b 100644
--- a/google/auth/_helpers.py
+++ b/google/auth/_helpers.py
@@ -20,8 +20,11 @@
 import urllib
 
 
-CLOCK_SKEW_SECS = 60  # 60 seconds
-CLOCK_SKEW = datetime.timedelta(seconds=CLOCK_SKEW_SECS)
+# Token server doesn't provide a new a token when doing refresh unless the
+# token is expiring within 30 seconds, so refresh threshold should not be
+# more than 30 seconds. Otherwise auth lib will send tons of refresh requests
+# until 30 seconds before the expiration, and cause a spike of CPU usage.
+REFRESH_THRESHOLD = datetime.timedelta(seconds=20)
 
 
 def copy_docstring(source_class):
diff --git a/google/auth/credentials.py b/google/auth/credentials.py
index 6356f54..8d9974c 100644
--- a/google/auth/credentials.py
+++ b/google/auth/credentials.py
@@ -62,7 +62,7 @@
 
         # Remove 10 seconds from expiry to err on the side of reporting
         # expiration early so that we avoid the 401-refresh-retry loop.
-        skewed_expiry = self.expiry - _helpers.CLOCK_SKEW
+        skewed_expiry = self.expiry - _helpers.REFRESH_THRESHOLD
         return _helpers.utcnow() >= skewed_expiry
 
     @property
diff --git a/google/auth/jwt.py b/google/auth/jwt.py
index 1bc7e5e..bb9ffae 100644
--- a/google/auth/jwt.py
+++ b/google/auth/jwt.py
@@ -167,12 +167,14 @@
     return header
 
 
-def _verify_iat_and_exp(payload):
+def _verify_iat_and_exp(payload, clock_skew_in_seconds=0):
     """Verifies the ``iat`` (Issued At) and ``exp`` (Expires) claims in a token
     payload.
 
     Args:
         payload (Mapping[str, str]): The JWT payload.
+        clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
+            validation.
 
     Raises:
         ValueError: if any checks failed.
@@ -188,7 +190,7 @@
     iat = payload["iat"]
     # Err on the side of accepting a token that is slightly early to account
     # for clock skew.
-    earliest = iat - _helpers.CLOCK_SKEW_SECS
+    earliest = iat - clock_skew_in_seconds
     if now < earliest:
         raise ValueError(
             "Token used too early, {} < {}. Check that your computer's clock is set correctly.".format(
@@ -200,12 +202,12 @@
     exp = payload["exp"]
     # Err on the side of accepting a token that is slightly out of date
     # to account for clow skew.
-    latest = exp + _helpers.CLOCK_SKEW_SECS
+    latest = exp + clock_skew_in_seconds
     if latest < now:
         raise ValueError("Token expired, {} < {}".format(latest, now))
 
 
-def decode(token, certs=None, verify=True, audience=None):
+def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds=0):
     """Decode and verify a JWT.
 
     Args:
@@ -221,6 +223,8 @@
         audience (str or list): The audience claim, 'aud', that this JWT should
             contain. Or a list of audience claims. If None then the JWT's 'aud'
             parameter is not verified.
+        clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
+            validation.
 
     Returns:
         Mapping[str, str]: The deserialized JSON payload in the JWT.
@@ -271,7 +275,7 @@
         raise ValueError("Could not verify token signature.")
 
     # Verify the issued at and created times in the payload.
-    _verify_iat_and_exp(payload)
+    _verify_iat_and_exp(payload, clock_skew_in_seconds)
 
     # Check audience.
     if audience is not None:
diff --git a/google/oauth2/credentials.py b/google/oauth2/credentials.py
index e259f78..6d34edf 100644
--- a/google/oauth2/credentials.py
+++ b/google/oauth2/credentials.py
@@ -270,7 +270,7 @@
                 raise exceptions.RefreshError(
                     "The refresh_handler returned expiry is not a datetime object."
                 )
-            if _helpers.utcnow() >= expiry - _helpers.CLOCK_SKEW:
+            if _helpers.utcnow() >= expiry - _helpers.REFRESH_THRESHOLD:
                 raise exceptions.RefreshError(
                     "The credentials returned by the refresh_handler are "
                     "already expired."
@@ -359,7 +359,7 @@
                 expiry.rstrip("Z").split(".")[0], "%Y-%m-%dT%H:%M:%S"
             )
         else:
-            expiry = _helpers.utcnow() - _helpers.CLOCK_SKEW
+            expiry = _helpers.utcnow() - _helpers.REFRESH_THRESHOLD
 
         # process scopes, which needs to be a seq
         if scopes is None and "scopes" in info:
diff --git a/tests/compute_engine/test_credentials.py b/tests/compute_engine/test_credentials.py
index ebe9aa5..81cc6db 100644
--- a/tests/compute_engine/test_credentials.py
+++ b/tests/compute_engine/test_credentials.py
@@ -64,7 +64,7 @@
 
     @mock.patch(
         "google.auth._helpers.utcnow",
-        return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
+        return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
     )
     @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
     def test_refresh_success(self, get, utcnow):
@@ -98,7 +98,7 @@
 
     @mock.patch(
         "google.auth._helpers.utcnow",
-        return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
+        return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
     )
     @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
     def test_refresh_success_with_scopes(self, get, utcnow):
diff --git a/tests/oauth2/test_credentials.py b/tests/oauth2/test_credentials.py
index b6a80e3..243f97d 100644
--- a/tests/oauth2/test_credentials.py
+++ b/tests/oauth2/test_credentials.py
@@ -115,7 +115,7 @@
     @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
     @mock.patch(
         "google.auth._helpers.utcnow",
-        return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
+        return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
     )
     def test_refresh_success(self, unused_utcnow, refresh_grant):
         token = "token"
@@ -175,7 +175,7 @@
     @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
     @mock.patch(
         "google.auth._helpers.utcnow",
-        return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
+        return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
     )
     def test_refresh_with_refresh_token_and_refresh_handler(
         self, unused_utcnow, refresh_grant
@@ -361,7 +361,7 @@
 
     @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min)
     def test_refresh_with_refresh_handler_expired_token(self, unused_utcnow):
-        expected_expiry = datetime.datetime.min + _helpers.CLOCK_SKEW
+        expected_expiry = datetime.datetime.min + _helpers.REFRESH_THRESHOLD
         # Simulate refresh handler returns an expired token.
         refresh_handler = mock.Mock(return_value=("TOKEN", expected_expiry))
         scopes = ["email", "profile"]
@@ -391,7 +391,7 @@
     @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
     @mock.patch(
         "google.auth._helpers.utcnow",
-        return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
+        return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
     )
     def test_credentials_with_scopes_requested_refresh_success(
         self, unused_utcnow, refresh_grant
@@ -457,7 +457,7 @@
     @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
     @mock.patch(
         "google.auth._helpers.utcnow",
-        return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
+        return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
     )
     def test_credentials_with_only_default_scopes_requested(
         self, unused_utcnow, refresh_grant
@@ -521,7 +521,7 @@
     @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
     @mock.patch(
         "google.auth._helpers.utcnow",
-        return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
+        return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
     )
     def test_credentials_with_scopes_returned_refresh_success(
         self, unused_utcnow, refresh_grant
@@ -588,7 +588,7 @@
     @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
     @mock.patch(
         "google.auth._helpers.utcnow",
-        return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
+        return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
     )
     def test_credentials_with_scopes_refresh_failure_raises_refresh_error(
         self, unused_utcnow, refresh_grant
diff --git a/tests/test_credentials.py b/tests/test_credentials.py
index 0633b38..2de6388 100644
--- a/tests/test_credentials.py
+++ b/tests/test_credentials.py
@@ -46,7 +46,9 @@
     # Set the expiration to one second more than now plus the clock skew
     # accomodation. These credentials should be valid.
     credentials.expiry = (
-        datetime.datetime.utcnow() + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1)
+        datetime.datetime.utcnow()
+        + _helpers.REFRESH_THRESHOLD
+        + datetime.timedelta(seconds=1)
     )
 
     assert credentials.valid
diff --git a/tests/test_downscoped.py b/tests/test_downscoped.py
index 795ec29..9ca95f5 100644
--- a/tests/test_downscoped.py
+++ b/tests/test_downscoped.py
@@ -669,7 +669,9 @@
         # Set the expiration to one second more than now plus the clock skew
         # accommodation. These credentials should be valid.
         credentials.expiry = (
-            datetime.datetime.min + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1)
+            datetime.datetime.min
+            + _helpers.REFRESH_THRESHOLD
+            + datetime.timedelta(seconds=1)
         )
 
         assert credentials.valid
diff --git a/tests/test_external_account.py b/tests/test_external_account.py
index e8297da..df6174f 100644
--- a/tests/test_external_account.py
+++ b/tests/test_external_account.py
@@ -976,7 +976,9 @@
         # Set the expiration to one second more than now plus the clock skew
         # accomodation. These credentials should be valid.
         credentials.expiry = (
-            datetime.datetime.min + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1)
+            datetime.datetime.min
+            + _helpers.REFRESH_THRESHOLD
+            + datetime.timedelta(seconds=1)
         )
 
         assert credentials.valid
@@ -1027,7 +1029,9 @@
         # Set the expiration to one second more than now plus the clock skew
         # accomodation. These credentials should be valid.
         credentials.expiry = (
-            datetime.datetime.min + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1)
+            datetime.datetime.min
+            + _helpers.REFRESH_THRESHOLD
+            + datetime.timedelta(seconds=1)
         )
 
         assert credentials.valid
diff --git a/tests/test_iam.py b/tests/test_iam.py
index 30ce227..e9eca58 100644
--- a/tests/test_iam.py
+++ b/tests/test_iam.py
@@ -45,7 +45,7 @@
             super(CredentialsImpl, self).__init__()
             self.token = "token"
             # Force refresh
-            self.expiry = datetime.datetime.min + _helpers.CLOCK_SKEW
+            self.expiry = datetime.datetime.min + _helpers.REFRESH_THRESHOLD
 
         def refresh(self, request):
             pass
diff --git a/tests/test_impersonated_credentials.py b/tests/test_impersonated_credentials.py
index 126c4c3..3dbb6ca 100644
--- a/tests/test_impersonated_credentials.py
+++ b/tests/test_impersonated_credentials.py
@@ -211,11 +211,11 @@
         credentials = self.make_credentials(lifetime=None)
 
         # Source credentials is refreshed only if it is expired within
-        # _helpers.CLOCK_SKEW from now. We add a time_skew to the expiry, so
+        # _helpers.REFRESH_THRESHOLD from now. We add a time_skew to the expiry, so
         # source credentials is refreshed only if time_skew <= 0.
         credentials._source_credentials.expiry = (
             _helpers.utcnow()
-            + _helpers.CLOCK_SKEW
+            + _helpers.REFRESH_THRESHOLD
             + datetime.timedelta(seconds=time_skew)
         )
         credentials._source_credentials.token = "Token"
@@ -238,7 +238,7 @@
             assert not credentials.expired
 
             # Source credentials is refreshed only if it is expired within
-            # _helpers.CLOCK_SKEW
+            # _helpers.REFRESH_THRESHOLD
             if time_skew > 0:
                 source_cred_refresh.assert_not_called()
             else:
diff --git a/tests/test_jwt.py b/tests/test_jwt.py
index 0dd7fa9..ba7277c 100644
--- a/tests/test_jwt.py
+++ b/tests/test_jwt.py
@@ -197,7 +197,7 @@
         }
     )
     with pytest.raises(ValueError) as excinfo:
-        jwt.decode(token, PUBLIC_CERT_BYTES)
+        jwt.decode(token, PUBLIC_CERT_BYTES, clock_skew_in_seconds=59)
     assert excinfo.match(r"Token used too early")
 
 
@@ -210,10 +210,40 @@
         }
     )
     with pytest.raises(ValueError) as excinfo:
-        jwt.decode(token, PUBLIC_CERT_BYTES)
+        jwt.decode(token, PUBLIC_CERT_BYTES, clock_skew_in_seconds=59)
     assert excinfo.match(r"Token expired")
 
 
+def test_decode_success_with_no_clock_skew(token_factory):
+    token = token_factory(
+        claims={
+            "exp": _helpers.datetime_to_secs(
+                _helpers.utcnow() + datetime.timedelta(seconds=1)
+            ),
+            "iat": _helpers.datetime_to_secs(
+                _helpers.utcnow() - datetime.timedelta(seconds=1)
+            ),
+        }
+    )
+
+    jwt.decode(token, PUBLIC_CERT_BYTES)
+
+
+def test_decode_success_with_custom_clock_skew(token_factory):
+    token = token_factory(
+        claims={
+            "exp": _helpers.datetime_to_secs(
+                _helpers.utcnow() + datetime.timedelta(seconds=2)
+            ),
+            "iat": _helpers.datetime_to_secs(
+                _helpers.utcnow() - datetime.timedelta(seconds=2)
+            ),
+        }
+    )
+
+    jwt.decode(token, PUBLIC_CERT_BYTES, clock_skew_in_seconds=1)
+
+
 def test_decode_bad_token_wrong_audience(token_factory):
     token = token_factory()
     audience = "audience2@example.com"
diff --git a/tests/transport/test_grpc.py b/tests/transport/test_grpc.py
index 926c1bc..3437658 100644
--- a/tests/transport/test_grpc.py
+++ b/tests/transport/test_grpc.py
@@ -80,7 +80,7 @@
 
     def test_call_refresh(self):
         credentials = CredentialsStub()
-        credentials.expiry = datetime.datetime.min + _helpers.CLOCK_SKEW
+        credentials.expiry = datetime.datetime.min + _helpers.REFRESH_THRESHOLD
         request = mock.create_autospec(transport.Request)
 
         plugin = google.auth.transport.grpc.AuthMetadataPlugin(credentials, request)
diff --git a/tests_async/oauth2/test_credentials_async.py b/tests_async/oauth2/test_credentials_async.py
index bc89392..06c9141 100644
--- a/tests_async/oauth2/test_credentials_async.py
+++ b/tests_async/oauth2/test_credentials_async.py
@@ -62,7 +62,7 @@
     @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True)
     @mock.patch(
         "google.auth._helpers.utcnow",
-        return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
+        return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
     )
     @pytest.mark.asyncio
     async def test_refresh_success(self, unused_utcnow, refresh_grant):
@@ -124,7 +124,7 @@
     @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True)
     @mock.patch(
         "google.auth._helpers.utcnow",
-        return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
+        return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
     )
     @pytest.mark.asyncio
     async def test_credentials_with_scopes_requested_refresh_success(
@@ -188,7 +188,7 @@
     @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True)
     @mock.patch(
         "google.auth._helpers.utcnow",
-        return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
+        return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
     )
     @pytest.mark.asyncio
     async def test_credentials_with_scopes_returned_refresh_success(
@@ -251,7 +251,7 @@
     @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True)
     @mock.patch(
         "google.auth._helpers.utcnow",
-        return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
+        return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
     )
     @pytest.mark.asyncio
     async def test_credentials_with_scopes_refresh_failure_raises_refresh_error(
diff --git a/tests_async/test_credentials_async.py b/tests_async/test_credentials_async.py
index 0a48908..5315483 100644
--- a/tests_async/test_credentials_async.py
+++ b/tests_async/test_credentials_async.py
@@ -46,7 +46,9 @@
     # Set the expiration to one second more than now plus the clock skew
     # accomodation. These credentials should be valid.
     credentials.expiry = (
-        datetime.datetime.utcnow() + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1)
+        datetime.datetime.utcnow()
+        + _helpers.REFRESH_THRESHOLD
+        + datetime.timedelta(seconds=1)
     )
 
     assert credentials.valid