I'm trying to make a summary on responses for a Google Form. For that I use Google Forms API in Golang. I didn't find the Question text in the responses, just the QuestionId. So I decided to get the list of questions from the form itself. Surprisingly, I don't have issues with getting the responses, but I can't get the form.
I wrote the code based on Google Docs API Go Quickstart and modified it based on the Google Forms API doc.
Each time I run the program I get the Authentication error:
Unable to retrieve data from document: googleapi: Error 403: Request had insufficient authentication scopes.
Details:
[
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"domain": "googleapis.com",
"metadata": {
"method": "google.apps.forms.v1.FormsService.GetForm",
"service": "forms.googleapis.com"
},
"reason": "ACCESS_TOKEN_SCOPE_INSUFFICIENT"
}
]
Code is below:
// Retrieves a token, saves the token, then returns the generated client.
func getClient(config *oauth2.Config) *http.Client {
tokFile := "token.json"
tok, err := tokenFromFile(tokFile)
if err != nil {
tok = getTokenFromWeb(config)
saveToken(tokFile, tok)
}
return config.Client(context.Background(), tok)
}
// Requests a token from the web, then returns the retrieved token.
func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
fmt.Printf("Go to the following link in your browser then type the "+
"authorization code: \n%v\n", authURL)
var authCode string
if _, err := fmt.Scan(&authCode); err != nil {
log.Fatalf("Unable to read authorization code: %v", err)
}
tok, err := config.Exchange(oauth2.NoContext, authCode)
if err != nil {
log.Fatalf("Unable to retrieve token from web: %v", err)
}
return tok
}
// Retrieves a token from a local file.
func tokenFromFile(file string) (*oauth2.Token, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
tok := &oauth2.Token{}
err = json.NewDecoder(f).Decode(tok)
return tok, err
}
// Saves a token to a file path.
func saveToken(path string, token *oauth2.Token) {
fmt.Printf("Saving credential file to: %s\n", path)
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
log.Fatalf("Unable to cache OAuth token: %v", err)
}
defer f.Close()
json.NewEncoder(f).Encode(token)
}
var srv *forms.Service
var srvForm *forms.FormsService
func main() {
ctx := context.Background()
b, err := os.ReadFile("credentials.json")
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
config, err := google.ConfigFromJSON(b, forms.FormsBodyReadonlyScope, forms.FormsResponsesReadonlyScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
client := getClient(config)
srv, err = forms.NewService(ctx, option.WithHTTPClient(client), option.WithScopes(forms.DriveScope, forms.FormsResponsesReadonlyScope))
if err != nil {
log.Fatalf("Unable to retrieve Docs client: %v", err)
}
srvForm = forms.NewFormsService(srv)
if srvForm != nil {
docId := "HERE GOES ID OF A FORM"
doc, err := srvForm.Get(docId).Do()
if err != nil {
log.Fatalf("Unable to retrieve data from document: %v", err)
}
fmt.Printf("The title of the doc is: %s\n", doc.Items[0].Description)
for _, v := range doc.Items {
fmt.Printf("The description is: %s\n", v.Description)
fmt.Printf("The title is: %s\n", v.Title)
fmt.Printf("The title is: %s\n", v.QuestionItem.Question.QuestionId)
}
}
}
As shown in the examples here, it should be enough to provide "https://www.googleapis.com/auth/forms.body.readonly" as a Scope, but apparently it is not enough and even "https://www.googleapis.com/auth/drive" is not enough.
As you can see from the code, I've tried to provide the Authentication Scopes to google.ConfigFromJSON and to forms.NewService, but it doesn't help.
Apparently you can't just add the new scope to a program when token was created, it is necessary to delete the token and obtain new permission via the consent screen. After that code works fine.