Search code examples
pythongoogle-apigoogle-formsgoogle-forms-api

"Request had insufficient authentication scope" when trying to create google form through API


I am just getting into using the google API. As an exercise, I'm trying to create a custom form. I am using a service account key to authenticate. Then, I build a form consisting of just one question. Then, I am trying to create the form by sending a POST request. See code below:

from google.oauth2 import service_account
from googleapiclient.discovery import build

# set up authentication using the service account key
creds = service_account.Credentials.from_service_account_file(
    'my_service_account_key.json',
    scopes=['https://www.googleapis.com/auth/forms']
)
service = build('forms', 'v1', credentials=creds)
form_config = {
  'title': 'My Custom Form',
  'description': 'This is my custom form',
  'questions': [
    {
      'title': 'What is your name?',
      'type': 'short_answer'
    }
  ]
}

# create the form by sending a POST request to the API endpoint
response = service.forms().create(body=form_config).execute()

However, I am getting the following error:

HttpError: <HttpError 403 when requesting https://forms.googleapis.com/v1/forms?alt=json returned "Request had insufficient authentication scopes.".
Details: "[{'@type': 'type.googleapis.com/google.rpc.ErrorInfo', 'reason': 'ACCESS_TOKEN_SCOPE_INSUFFICIENT', 'domain': 'googleapis.com', 
'metadata': {'service': 'forms.googleapis.com', 'method': 'google.apps.forms.v1.FormsService.CreateForm'}}]">

I assume that my service account somehow does not have the correct scope permissions, but I can't find any clear information on how to change this. I've tried giving the service account all sorts of different roles.


Solution

  • With this code using a service account without impersonation will result in files owned by the service account, not by a user where the service account belongs. This will make Forms hard to find and work with. To configure impersonation in your code make sure you add an additional line, like so:

    SERVICE_ACCOUNT_FILE = 'serviceaccount.json'
    credentials = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes= SCOPES)
    delegated_creds = credentials.with_subject("[email protected]")
    

    If you don't have a Google Workspace organization you should instead use regular OAuth 2.0 with a OAuth consent screen.

    Also, the scope you are using is https://www.googleapis.com/auth/forms but the documentation at Forms.create reference states that to call the Forms.create method you need to use at least one of these:

    • https://www.googleapis.com/auth/drive
    • https://www.googleapis.com/auth/drive.file
    • https://www.googleapis.com/auth/forms.body

    Moreover, the documentation outlines that you need to create the Form first before you can add questions and other details. After the form is created successfully with the Forms.create method you will receive a response with the details of the create form.

    Domain-wide delegation is needed, follow the steps in this documentation to set up domain-wide delegation for a service account. In step #5 for "OAuth Scopes" make sure the same scopes you are using in your code are the ones authorized in the Admin console.

    After all necessary adjustments the final code is:

    from google.oauth2 import service_account
    from googleapiclient.discovery import build
    
    creds = service_account.Credentials.from_service_account_file(
        'serviceaccount.json',
        scopes=['https://www.googleapis.com/auth/forms.body']
    )
    
    delegated_creds = creds.with_subject("[email protected]")
    
    service = build('forms', 'v1', credentials=delegated_creds)
    
    form_config = {
      "info": {
        "title": "Newly create Form"
      }
    }
    
    response = service.forms().create(body=form_config).execute()
    
    print(response)
    

    Executing this code returned this response:

    {
        'formId': 'TheNewFormId',
        'info': {
            'title': 'Newly create Form',
            'documentTitle': 'Untitled form'
        },
        'revisionId': '00000002',
        'responderUri': 'https://docs.google.com/forms/d/e/TheNewObscuredFormId/viewform'
    }
    

    Note that the response contains the fileId, this can be used to update the information of the Form through code or the Form can be edited in the WebUI.