I want to create a grading bot for my community's applications through Google Forms and when I try to retrieve a form, I get a RefreshError
.
Minimal Reproducible Example:
# Google Credentials
from google.oauth2 import service_account
SCOPES = ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/forms.body'][0]
SERVICE_ACCOUNT_FILE = 'key.json'
credentials = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
import googleapiclient.discovery
forms = googleapiclient.discovery.build('forms', 'v1', credentials=credentials)
form_id = '__omitted__'
result = forms.forms().get(formId=form_id).execute()
print(result)
Log:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/google/oauth2/_client.py", line 323, in jwt_grant
access_token = response_data["access_token"]
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
KeyError: 'access_token'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "__omitted__", line 33, in <module>
result = forms.forms().get(formId=form_id).execute()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/googleapiclient/_helpers.py", line 130, in positional_wrapper
return wrapped(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/googleapiclient/http.py", line 923, in execute
resp, content = _retry_request(
^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/googleapiclient/http.py", line 191, in _retry_request
resp, content = http.request(uri, method, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/google_auth_httplib2.py", line 209, in request
self.credentials.before_request(self._request, method, uri, request_headers)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/google/auth/credentials.py", line 156, in before_request
self.refresh(request)
^^^^^^^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/google/oauth2/service_account.py", line 438, in refresh
access_token, expiry, _ = _client.jwt_grant(
^^^^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/google/oauth2/_client.py", line 328, in jwt_grant
six.raise_from(new_exc, caught_exc)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<string>", line 3, in raise_from
google.auth.exceptions.RefreshError: ('No access token in response.', {'id_token': '__omitted__'})
The service account has a group of Owner
with Forms API enabled.
Google Workspace services (including Form) represent content that includes user-owned data. For this reason, Google employs stricter controls on data access to these services and you can't (unlike Google Cloud services) use arbitrary (see below) Service Accounts to access these services.
Confusingly (!) Cloud IAM (manifest as an IAM role Owner
) is not implemented for Workspace services and role bindings have no effect. Workspace services rely on (the more limited) use of OAuth scopes.
The recommended solution only applies to paid|business plans for Workspace and requires the creation of a Service Account that has been delegated for domain-wide authority (this link is probably the most relevant but this is poorly documented and explained). Even with this specially constructed Service Account you must impersonate a specific user in order to access their content.
The hacky solution (caveat developer) that does not work in all cases involves adding an arbitrary Service Account's email address (generally {account}@{project}.iam.gserviceaccount.com
) to specific Workspace documents (e.g. a specific Forms document) as you would to share with a human user.
Corollary:
Ergo: Google discourages non-extension-based automation of its free Workspace services.