Search code examples
gogoogle-apigoogle-calendar-apiservice-accounts

How to set up golang client for Google Calendar APIs using a service account


I've seen lots of documentation on Google API clients for users, but very little on using a service account. This isn't on behalf of a user, I'm just trying to get a client working with the Calendar APIs using a client ID and client secret, which would be provided via environment variables for me (I'd prefer to not read from a file).

Here's what I have so far:

package main

import (
  "context"

  clientCredentials "golang.org/x/oauth2/clientcredentials"
  google "golang.org/x/oauth2/google"
  calendar "google.golang.org/api/calendar/v3"
  apiOption "google.golang.org/api/option"
)

func main() {
  config := &clientCredentials.Config{
    ClientID:     "<my_id>",
    ClientSecret: "-----BEGIN PRIVATE KEY-----\n...",
    TokenURL:     google.Endpoint.TokenURL,
  }
  ctx := context.Background()
  client := config.Client(ctx)
  service, _ := calendar.NewService(ctx, apiOption.WithHTTPClient(client))
  
  calendarList, err := service.CalendarList.List().Do()
}

But I'm getting the following error:

Get "https://www.googleapis.com/calendar/v3/users/me/calendarList?alt=json&prettyPrint=false": oauth2: cannot fetch token: 400 Bad Request
Response: {
  "error": "unsupported_grant_type",
  "error_description": "Invalid grant_type: client_credentials"
}

Any help here is greatly appreciated! I'm new to Golang, Oauth2, and Google APIs :)


Solution

  • The answer from @Tanaike got me on the right track. This is what I ended up using:

    package main
    
    import (
        "context"
        "encoding/json"
        "fmt"
    
        googleOauth "golang.org/x/oauth2/google"
        calendar "google.golang.org/api/calendar/v3"
        apiOption "google.golang.org/api/option"
    )
    
    var service *calendar.Service
    
    // Note that some of the fields are optional:
    type GoogleAuthConfig struct {
        Type                string `json:"type"`
        ProjectID           string `json:"project_id,omitempty"`
        ClientEmail         string `json:"client_email"`
        ClientID            string `json:"client_id,omitempty"`
        ClientSecret        string `json:"private_key"`
        ClientSecretID      string `json:"private_key_id,omitempty"`
        AuthURL             string `json:"auth_uri,omitempty"`
        TokenURL            string `json:"token_uri,omitempty"`
        AuthProviderCertURL string `json:"auth_provider_x509_cert_url,omitempty"`
        ClientCertURL       string `json:"client_x509_cert_url,omitempty"`
    }
    
    func main() {
        authConfig := GoogleAuthConfig{
            Type:         "service_account",
            ClientEmail:  "account123@project-456.iam.gserviceaccount.com",
            ClientID:     "1234",
            ClientSecret: "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
            AuthURL:      googleOauth.Endpoint.AuthURL,
            TokenURL:     googleOauth.Endpoint.TokenURL,
        }
        authConfigJson, err := json.Marshal(authConfig)
    
        ctx := context.Background()
        service, err = calendar.NewService(ctx, apiOption.WithCredentialsJSON([]byte(authConfigJson)))
    }
    

    Note that I didn't have to configure domain-wide delegation or impersonate a user; this worked fine after I added the service account to the calendar.

    Service accounts still need to accept calendar invites after you add the account email to the calendar. This can be done with the following:

    entry := calendar.CalendarListEntry{Id: calendarID}
    service.CalendarList.Insert(&entry).Do()