diff --git a/docs/source/api.rst b/docs/source/api.rst
index f5bcf95..8d90617 100644
--- a/docs/source/api.rst
+++ b/docs/source/api.rst
@@ -1,19 +1,18 @@
-.. currentmodule:: pandas_gbq
+.. currentmodule:: pydata_google_auth
 .. _api:
 
 *************
 API Reference
 *************
 
-.. note::
-
-  Only functions and classes which are members of the ``pandas_gbq`` module
-  are considered public. Submodules and their members are considered private.
-
 .. autosummary::
 
-   read_gbq
-   to_gbq
+   default
+   get_user_credentials
+   cache
 
-.. autofunction:: read_gbq
-.. autofunction:: to_gbq
+.. autofunction:: default
+.. autofunction:: get_user_credentials
+.. automodule:: pydata_google_auth.cache
+    :members:
+    :show-inheritance:
diff --git a/pydata_google_auth/auth.py b/pydata_google_auth/auth.py
index 47090e6..ae5197f 100644
--- a/pydata_google_auth/auth.py
+++ b/pydata_google_auth/auth.py
@@ -13,6 +13,7 @@ import oauthlib.oauth2.rfc6749.errors
 import google.auth.transport.requests
 
 from pydata_google_auth import exceptions
+from pydata_google_auth import cache
 
 
 logger = logging.getLogger(__name__)
@@ -21,17 +22,13 @@ CLIENT_ID = (
     "262006177488-3425ks60hkk80fssi9vpohv88g6q1iqd" ".apps.googleusercontent.com"
 )
 CLIENT_SECRET = "JSF-iczmzEgbTR-XK-2xaWAc"
-CREDENTIALS_DIRNAME = "pydata"
-CREDENTIALS_FILENAME = "pydata_google_credentials.json"
 
 
 def default(
     scopes,
-    client_id=CLIENT_ID,
-    client_secret=CLIENT_SECRET,
-    credentials_dirname=CREDENTIALS_DIRNAME,
-    credentials_filename=CREDENTIALS_FILENAME,
-    reauth=False,
+    client_id=None,
+    client_secret=None,
+    credentials_cache=cache.READ_WRITE,
     auth_local_webserver=False,
 ):
     # Try to retrieve Application Default Credentials
@@ -44,9 +41,7 @@ def default(
         scopes,
         client_id=client_id,
         client_secret=client_secret,
-        credentials_dirname=credentials_dirname,
-        credentials_filename=credentials_filename,
-        reauth=reauth,
+        credentials_cache=credentials_cache,
         auth_local_webserver=auth_local_webserver,
     )
 
@@ -92,14 +87,13 @@ def get_application_default_credentials(scopes):
 
 def get_user_credentials(
     scopes,
-    client_id=CLIENT_ID,
-    client_secret=CLIENT_SECRET,
-    credentials_dirname=CREDENTIALS_DIRNAME,
-    credentials_filename=CREDENTIALS_FILENAME,
-    reauth=False,
+    client_id=None,
+    client_secret=None,
+    credentials_cache=cache.READ_WRITE,
     auth_local_webserver=False,
 ):
-    """Gets user account credentials.
+    """
+    Gets user account credentials.
 
     This method authenticates using user credentials, either loading saved
     credentials from a file or by going through the OAuth flow.
@@ -113,16 +107,16 @@ def get_user_credentials(
     GoogleCredentials : credentials
         Credentials for the user with BigQuery access.
     """
-    # Use the default credentials location under ~/.config and the
-    # equivalent directory on windows if the user has not specified a
-    # credentials path.
-    credentials_path = get_default_credentials_path(
-        credentials_dirname, credentials_filename
-    )
+    # Use None as default for client_id and client_secret so that the values
+    # aren't included in the docs. A string of bytes isn't useful for the
+    # documentation and might encourage the values to be used outside of this
+    # library.
+    if client_id is None:
+        client_id = CLIENT_ID
+    if client_secret is None:
+        client_secret = CLIENT_SECRET
 
-    credentials = None
-    if not reauth:
-        credentials = load_user_credentials_from_file(credentials_path)
+    credentials = credentials_cache.load()
 
     client_config = {
         "installed": {
@@ -149,100 +143,10 @@ def get_user_credentials(
                 "Unable to get valid credentials: {0}".format(exc)
             )
 
-        save_user_account_credentials(credentials, credentials_path)
+        credentials_cache.save(credentials)
 
     if credentials and not credentials.valid:
         request = google.auth.transport.requests.Request()
         credentials.refresh(request)
 
     return credentials
-
-
-def load_user_credentials_from_info(credentials_json):
-    return google.oauth2.credentials.Credentials(
-        token=credentials_json.get("access_token"),
-        refresh_token=credentials_json.get("refresh_token"),
-        id_token=credentials_json.get("id_token"),
-        token_uri=credentials_json.get("token_uri"),
-        client_id=credentials_json.get("client_id"),
-        client_secret=credentials_json.get("client_secret"),
-        scopes=credentials_json.get("scopes"),
-    )
-
-
-def load_user_credentials_from_file(credentials_path):
-    """
-    Loads user account credentials from a local file.
-
-    .. versionadded 0.2.0
-
-    Parameters
-    ----------
-    None
-
-    Returns
-    -------
-    - GoogleCredentials,
-        If the credentials can loaded. The retrieved credentials should
-        also have access to the project (project_id) on BigQuery.
-    - OR None,
-        If credentials can not be loaded from a file. Or, the retrieved
-        credentials do not have access to the project (project_id)
-        on BigQuery.
-    """
-    try:
-        with open(credentials_path) as credentials_file:
-            credentials_json = json.load(credentials_file)
-    except (IOError, ValueError) as exc:
-        logger.debug(
-            "Error loading credentials from {}: {}".format(credentials_path, str(exc))
-        )
-        return None
-
-    return load_user_credentials_from_info(credentials_json)
-
-
-def get_default_credentials_path(credentials_dirname, credentials_filename):
-    """
-    Gets the default path to the Google user credentials
-
-    .. versionadded 0.3.0
-
-    Returns
-    -------
-    Path to the Google user credentials
-    """
-    if os.name == "nt":
-        config_path = os.environ["APPDATA"]
-    else:
-        config_path = os.path.join(os.path.expanduser("~"), ".config")
-
-    config_path = os.path.join(config_path, credentials_dirname)
-
-    # Create a pydata directory in an application-specific hidden
-    # user folder on the operating system.
-    if not os.path.exists(config_path):
-        os.makedirs(config_path)
-
-    return os.path.join(config_path, credentials_filename)
-
-
-def save_user_account_credentials(credentials, credentials_path):
-    """
-    Saves user account credentials to a local file.
-
-    .. versionadded 0.2.0
-    """
-    try:
-        with open(credentials_path, "w") as credentials_file:
-            credentials_json = {
-                "refresh_token": credentials.refresh_token,
-                "id_token": credentials.id_token,
-                "token_uri": credentials.token_uri,
-                "client_id": credentials.client_id,
-                "client_secret": credentials.client_secret,
-                "scopes": credentials.scopes,
-            }
-            json.dump(credentials_json, credentials_file)
-    except IOError:
-        logger.warning("Unable to save credentials.")
diff --git a/pydata_google_auth/cache.py b/pydata_google_auth/cache.py
new file mode 100644
index 0000000..c755dd0
--- /dev/null
+++ b/pydata_google_auth/cache.py
@@ -0,0 +1,233 @@
+"""Caching implementations for reading and writing user credentials."""
+
+import json
+import logging
+import os
+import os.path
+
+import google.oauth2.credentials
+
+
+logger = logging.getLogger(__name__)
+
+
+_DIRNAME = "pydata"
+_FILENAME = "pydata_google_credentials.json"
+
+
+def _get_default_credentials_path(credentials_dirname, credentials_filename):
+    """
+    Gets the default path to the Google user credentials
+
+    Returns
+    -------
+    str
+        Path to the Google user credentials
+    """
+    if os.name == "nt":
+        config_path = os.environ["APPDATA"]
+    else:
+        config_path = os.path.join(os.path.expanduser("~"), ".config")
+
+    config_path = os.path.join(config_path, credentials_dirname)
+
+    # Create a pydata directory in an application-specific hidden
+    # user folder on the operating system.
+    if not os.path.exists(config_path):
+        os.makedirs(config_path)
+
+    return os.path.join(config_path, credentials_filename)
+
+
+def _load_user_credentials_from_info(credentials_json):
+    return google.oauth2.credentials.Credentials(
+        token=credentials_json.get("access_token"),
+        refresh_token=credentials_json.get("refresh_token"),
+        id_token=credentials_json.get("id_token"),
+        token_uri=credentials_json.get("token_uri"),
+        client_id=credentials_json.get("client_id"),
+        client_secret=credentials_json.get("client_secret"),
+        scopes=credentials_json.get("scopes"),
+    )
+
+
+def _load_user_credentials_from_file(credentials_path):
+    """
+    Loads user account credentials from a local file.
+
+    Parameters
+    ----------
+    None
+
+    Returns
+    -------
+    - GoogleCredentials,
+        If the credentials can loaded. The retrieved credentials should
+        also have access to the project (project_id) on BigQuery.
+    - OR None,
+        If credentials can not be loaded from a file. Or, the retrieved
+        credentials do not have access to the project (project_id)
+        on BigQuery.
+    """
+    try:
+        with open(credentials_path) as credentials_file:
+            credentials_json = json.load(credentials_file)
+    except (IOError, ValueError) as exc:
+        logger.debug(
+            "Error loading credentials from {}: {}".format(credentials_path, str(exc))
+        )
+        return None
+
+    return _load_user_credentials_from_info(credentials_json)
+
+
+def _save_user_account_credentials(credentials, credentials_path):
+    """
+    Saves user account credentials to a local file.
+    """
+    try:
+        with open(credentials_path, "w") as credentials_file:
+            credentials_json = {
+                "refresh_token": credentials.refresh_token,
+                "id_token": credentials.id_token,
+                "token_uri": credentials.token_uri,
+                "client_id": credentials.client_id,
+                "client_secret": credentials.client_secret,
+                "scopes": credentials.scopes,
+            }
+            json.dump(credentials_json, credentials_file)
+    except IOError:
+        logger.warning("Unable to save credentials.")
+
+
+class CredentialsCache(object):
+    """
+    Shared base class for crentials classes.
+
+    This class also functions as a noop implementation of a credentials class.
+    """
+
+    def load(self):
+        """
+        Load credentials from disk.
+
+        Does nothing in this base class.
+
+        Returns
+        -------
+        google.oauth2.credentials.Credentials, optional
+            Returns user account credentials loaded from disk or ``None`` if no
+            credentials could be found.
+        """
+        pass
+
+    def save(self, credentials):
+        """
+        Write credentials to disk.
+
+        Does nothing in this base class.
+
+        Parameters
+        ----------
+        credentials : google.oauth2.credentials.Credentials
+            User credentials to save to disk.
+        """
+        pass
+
+
+class ReadWriteCredentialsCache(CredentialsCache):
+    """
+    A :class:`~pydata_google_auth.cache.CredentialsCache` which writes to
+    disk and reads cached credentials from disk.
+
+    Parameters
+    ----------
+    dirname : str, optional
+        Name of directory to write credentials to. This directory is created
+        within the ``.config`` subdirectory of the ``HOME`` (``APPDATA`` on
+        Windows) directory.
+    filename : str, optional
+        Name of the credentials file within the credentials directory.
+    """
+
+    def __init__(self, dirname=_DIRNAME, filename=_FILENAME):
+        super(ReadWriteCredentialsCache, self).__init__()
+        self._path = _get_default_credentials_path(_DIRNAME, _FILENAME)
+
+    def load(self):
+        """
+        Load credentials from disk.
+
+        Returns
+        -------
+        google.oauth2.credentials.Credentials, optional
+            Returns user account credentials loaded from disk or ``None`` if no
+            credentials could be found.
+        """
+        return _load_user_credentials_from_file(self._path)
+
+    def save(self, credentials):
+        """
+        Write credentials to disk.
+
+        Parameters
+        ----------
+        credentials : google.oauth2.credentials.Credentials
+            User credentials to save to disk.
+        """
+        _save_user_account_credentials(credentials, self._path)
+
+
+class WriteOnlyCredentialsCache(CredentialsCache):
+    """
+    A :class:`~pydata_google_auth.cache.CredentialsCache` which writes to
+    disk, but doesn't read from disk.
+
+    Use this class to reauthorize against Google APIs and cache your
+    credentials for later.
+
+    Parameters
+    ----------
+    dirname : str, optional
+        Name of directory to write credentials to. This directory is created
+        within the ``.config`` subdirectory of the ``HOME`` (``APPDATA`` on
+        Windows) directory.
+    filename : str, optional
+        Name of the credentials file within the credentials directory.
+    """
+
+    def __init__(self, dirname=_DIRNAME, filename=_FILENAME):
+        super(WriteOnlyCredentialsCache, self).__init__()
+        self._path = _get_default_credentials_path(_DIRNAME, _FILENAME)
+
+    def save(self, credentials):
+        """
+        Write credentials to disk.
+
+        Parameters
+        ----------
+        credentials : google.oauth2.credentials.Credentials
+            User credentials to save to disk.
+        """
+        _save_user_account_credentials(credentials, self._path)
+
+
+NOOP = CredentialsCache()
+"""
+Noop impmentation of credentials cache.
+
+This cache always reauthorizes and never save credentials to disk.
+Recommended for shared machines.
+"""
+
+READ_WRITE = ReadWriteCredentialsCache()
+"""
+Write credentials to disk and read cached credentials from disk.
+"""
+
+REAUTH = WriteOnlyCredentialsCache()
+"""
+Write credentials to disk. Never read cached credentials from disk.
+
+Use this to reauthenticate and refresh the cached credentials.
+"""

Implement CredentialsCache base classes
From: https://github.com/pydata/pandas-gbq/issues/161

The credentials caching behavior should be configurable via `CredentialsCache`,`WriteOnlyCredentialsCache`, and `NoopCredentialsCache`. This replaces the `reauth` argument and `PANDAS_GBQ_CREDENTIALS_FILE` environment variables.
diff --git a/tests/system/test_auth.py b/tests/system/test_auth.py
index 52409cb..9c182bc 100644
--- a/tests/system/test_auth.py
+++ b/tests/system/test_auth.py
@@ -47,17 +47,29 @@ def test_get_user_credentials_gets_valid_credentials():
     assert credentials.has_scopes(TEST_SCOPES)
 
 
-def test_get_user_credentials_from_file_gets_valid_credentials():
+def test_get_user_credentials_noop_gets_valid_credentials():
     import pydata_google_auth
-    import pydata_google_auth.auth
+    import pydata_google_auth.cache
 
-    # Mock load_user_credentials_from_file to fail, forcing fresh credentials.
-    with mock.patch(
-        "pydata_google_auth.auth.load_user_credentials_from_file", return_value=None
-    ):
-        credentials = pydata_google_auth.get_user_credentials(
-            TEST_SCOPES, auth_local_webserver=True
-        )
+    credentials = pydata_google_auth.get_user_credentials(
+        TEST_SCOPES,
+        credentials_cache=pydata_google_auth.cache.NOOP,
+        auth_local_webserver=True,
+    )
+
+    assert credentials.valid
+    assert credentials.has_scopes(TEST_SCOPES)
+
+
+def test_get_user_credentials_reauth_gets_valid_credentials():
+    import pydata_google_auth
+    import pydata_google_auth.cache
+
+    credentials = pydata_google_auth.get_user_credentials(
+        TEST_SCOPES,
+        credentials_cache=pydata_google_auth.cache.REAUTH,
+        auth_local_webserver=True,
+    )
 
     assert credentials.valid
     assert credentials.has_scopes(TEST_SCOPES)
diff --git a/tests/unit/test_auth.py b/tests/unit/test_auth.py
index 2c9ed11..89b882a 100644
--- a/tests/unit/test_auth.py
+++ b/tests/unit/test_auth.py
@@ -10,6 +10,7 @@ except ImportError:  # pragma: NO COVER
 
 import google.auth
 import google.auth.credentials
+import google.oauth2.credentials
 
 
 TEST_SCOPES = ["https://www.googleapis.com/auth/cloud-platform"]
@@ -33,18 +34,17 @@ def test_default_returns_google_auth_credentials(monkeypatch):
 
 def test_default_loads_user_credentials(monkeypatch):
     from pydata_google_auth import auth
+    from pydata_google_auth import cache
 
     def mock_default_credentials(scopes=None, request=None):
         return (None, None)
 
     monkeypatch.setattr(google.auth, "default", mock_default_credentials)
-    mock_user_credentials = mock.create_autospec(google.auth.credentials.Credentials)
 
-    def mock_load_credentials(project_id=None, credentials_path=None):
-        return mock_user_credentials
+    mock_cache = mock.create_autospec(cache.CredentialsCache)
+    mock_user_credentials = mock.create_autospec(google.oauth2.credentials.Credentials)
+    mock_cache.load.return_value = mock_user_credentials
 
-    monkeypatch.setattr(auth, "load_user_credentials_from_file", mock_load_credentials)
-
-    credentials, project = auth.default(TEST_SCOPES)
+    credentials, project = auth.default(TEST_SCOPES, credentials_cache=mock_cache)
     assert project is None
     assert credentials is mock_user_credentials

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
cachetools==5.5.2
certifi==2025.1.31
charset-normalizer==3.4.1
coverage==7.8.0
exceptiongroup==1.2.2
execnet==2.1.1
google-api-core==2.24.2
google-auth==2.38.0
google-auth-oauthlib==1.2.1
google-cloud-bigquery==3.31.0
google-cloud-core==2.4.3
google-crc32c==1.7.1
google-resumable-media==2.7.2
googleapis-common-protos==1.69.2
grpcio==1.71.0
grpcio-status==1.71.0
idna==3.10
iniconfig==2.1.0
numpy==2.0.2
oauthlib==3.2.2
packaging==24.2
pandas==2.2.3
pluggy==1.5.0
proto-plus==1.26.1
protobuf==5.29.4
pyasn1==0.6.1
pyasn1_modules==0.4.2
-e git+https://github.com/pydata/pydata-google-auth.git@3564ac45831ca576cdfeeac28687268f782e0b15#egg=pydata_google_auth
pytest==8.3.5
pytest-asyncio==0.26.0
pytest-cov==6.0.0
pytest-mock==3.14.0
pytest-xdist==3.6.1
python-dateutil==2.9.0.post0
pytz==2025.2
requests==2.32.3
requests-oauthlib==2.0.0
rsa==4.9
six==1.17.0
tomli==2.2.1
tqdm==4.67.1
typing_extensions==4.13.0
tzdata==2025.2
urllib3==2.3.0

name: pydata-google-auth
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - cachetools==5.5.2
      - certifi==2025.1.31
      - charset-normalizer==3.4.1
      - coverage==7.8.0
      - exceptiongroup==1.2.2
      - execnet==2.1.1
      - google-api-core==2.24.2
      - google-auth==2.38.0
      - google-auth-oauthlib==1.2.1
      - google-cloud-bigquery==3.31.0
      - google-cloud-core==2.4.3
      - google-crc32c==1.7.1
      - google-resumable-media==2.7.2
      - googleapis-common-protos==1.69.2
      - grpcio==1.71.0
      - grpcio-status==1.71.0
      - idna==3.10
      - iniconfig==2.1.0
      - numpy==2.0.2
      - oauthlib==3.2.2
      - packaging==24.2
      - pandas==2.2.3
      - pluggy==1.5.0
      - proto-plus==1.26.1
      - protobuf==5.29.4
      - pyasn1==0.6.1
      - pyasn1-modules==0.4.2
      - pytest==8.3.5
      - pytest-asyncio==0.26.0
      - pytest-cov==6.0.0
      - pytest-mock==3.14.0
      - pytest-xdist==3.6.1
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - requests==2.32.3
      - requests-oauthlib==2.0.0
      - rsa==4.9
      - six==1.17.0
      - tomli==2.2.1
      - tqdm==4.67.1
      - typing-extensions==4.13.0
      - tzdata==2025.2
      - urllib3==2.3.0
prefix: /opt/conda/envs/pydata-google-auth

tests/unit/test_auth.py::test_default_loads_user_credentials
tests/system/test_auth.py::test_default_gets_valid_credentials tests/system/test_auth.py::test_default_gets_user_credentials tests/system/test_auth.py::test_get_user_credentials_gets_valid_credentials tests/system/test_auth.py::test_get_user_credentials_noop_gets_valid_credentials tests/system/test_auth.py::test_get_user_credentials_reauth_gets_valid_credentials
tests/unit/test_auth.py::test_default_returns_google_auth_credentials
pydata_google_auth/auth.py docs/source/api.rst pydata_google_auth/cache.py
pydata_google_auth/auth.py docs/source/api.rst pydata_google_auth/cache.py