Blog post -

Using a Service Account for accessing Google Calendar API

Uh, what? Displaying events from Google Calendar to another calendar? Well yes, so let’s take it from the beginning.

We’ve been resurrecting a legacy project lately… Dashboards!

We use them to display some useful information around the office (you’ll notice them, if you happen to drop by to say hi).

And since we like Ruby a lot, we use Smashing (spiritual successor of the beloved Dashing) which is a convenient way to mount data on flexible layouts. It has lots of pre-made widgets, it's easily configurable and it well adapts to large TV/monitors. It’s also designer-approved, which is a great plus. :-D

For the case in exam, we want to show the list of scheduled meetings, taken from our company calendar. For this, we want to connect to Google Calendar and get a list.

The old system was using a deprecated API version, and it used a token based authentication – a personal Google Account used only with the purpose of giving access to calendar events. That had a few flaws. One over all, the refresh token expired regularly, forcing the system admin to connect every now and then to re-login and refresh the access token. Very nice indeed.

We needed a way to establish a direct connection between Smashing and the Calendar API. The correct way to set it up, in this case, would be to use a Google Service Account. But we’ll come back to it later, let’s have a look at the Google Calendar’s API first.

Get to know your API

Google’s official documentation provides pretty good information on how to connect to the Calendar API:

For listing events on your calendar, you’d probably need to have a look at the options for CalendarList/list and Events/list endpoints. At the same url the API explorer is available, which will allow us to try out the API.

How to get access credentials?

First, visit your Google API console ( https://console.developers.google.com/apis). If you haven’t done it already, click on "Enable APIs and services", search for Google Calendar and enable it.

Then visit the Credentials tab, then click on Create Credentials. What kind of credentials do we need?

If you have read the docs, or quickly tried the API Explorer, you’ll find out pretty soon that access to the list of Calendar Events is limited to Authorized Scopes. Which means, for instance, that we can’t use a token of type “API”. We’re left with OAuth client ID and Service account.

If choosing OAuth client ID would help us to create a client application that will ask the user to authenticate himself/herself before we can make any call to the API, it isn't what we need here. This is fine if your application reads to your user’s calendar and/or where it can actually prompt the user for authorisation.
In our case, instead, we won’t have any way of prompting for user interaction. So we’ll have to connect the application server side.

We’re going to create a Service Account key.

Create a new account, give it a proper name, and select from the dropdown a default role. In this case "project viewer" should be enough (and we are whitelisting access to our dashboards in other ways, so we don't need so much access control granularity).

Once you click on “Create” you’ll be served with a file to download.
Store it in a safe place for the moment, we’re going to use it later.

But first, let’s set up a sample request.

A connection dummy

This really depends on where you have to use the results of your queries. In our case, we’re going to create a new Smashing Job. For the scope of showing how it works, any script will suffice, or run it in a Ruby console.

Once again, Google’s official documentation provides a good bootstrap:

The suggested gem (https://github.com/googleapis/google-auth-library-ruby) also shows some good connection examples for our case.

Let’s add the suggested Ruby client to your Gemfile:

gem "omniauth-google-oauth2"

Then setup a sample connection scripts to get our events:

# calendar.rb
require "dotenv"
require "googleauth"
require "google/apis/calendar_v3"
Dotenv.load

class CalendarAPIClient
  def initialize
    @client #it initializes the connection
  end

  def events(calendar_id)
    # it gets a list of events
  end

  def calendars
    # it gets a list of calendars
  end
end

calendar = CalendarAPIClient.new

puts calendar.calendars
puts calendar.events("yourcalendarid@resource.calendar.google.com")

As you can see, the code is very basic. There are a few hidden defaults though. For instance: where are the credentials?

We’re using Application’s Default Credentials, as explained more in detail at:https://cloud.google.com/docs/authentication/production. Meaning that our access credentials are configured by setting up a few environment variables, which for a Service Account are:

# "GOOGLE_CLIENT_ID
# "GOOGLE_CLIENT_EMAIL
# "GOOGLE_ACCOUNT_TYPE
# "GOOGLE_PRIVATE_KEY

We use DotEnv https://github.com/bkeepers/dotenv for our convenience. Alternatively just export your ENV vars:

export GOOGLE_ACCOUNT_TYPE=service_account
export GOOGLE_CLIENT_ID=000000000000000000000
export GOOGLE_CLIENT_EMAIL=xxxx@xxxx.iam.gserviceaccount.com
export GOOGLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n
...
\n-----END PRIVATE KEY-----\n"

Or run the script as:

GOOGLE_ACCOUNT_TYPE=service_account ... calendar.rb

To populate those ENV vars, use the values you get from the Service Account key you downloaded in the previous step. This is particularly useful is you’re going to use the script in different environments, or deploy it on Heroku.

Of course, this won’t output anything yet. Let’s make it do some real work:

class CalendarAPIClient
  attr_accessor :name

  def initialize
    scopes = [
      "https://www.googleapis.com/auth/calendar",
    ]

    # it initializes the connection, using the credentials set from ENV
    @client = ::Google::Auth.get_application_default(scopes)
    @service = Google::Apis::CalendarV3::CalendarService.new

    # it sets the authentication method and if fetches access token
    @service.authorization = @client
    @service.authorization.fetch_access_token!
  end

  def events(calendar_id)
    @service.list_events(calendar_id)
  end

  def calendars
    @service.list_calendar_lists
  end
end

It’s a bit fancier now. Still it doesn’t output anything. How come?

Giving access

You’d be tempted to check you have inserted the right calendar ID and try again. And by the way, you can find the calendar ID here: visit https://calendar.google.com, locate your Calendar in the list on the left list, click on Settings then scroll to “Integrate calendar”. There’s the Calendar ID.

However, that’s not enough to access. If you run the script, the list of calendars will be empty. It’s because the Service account doesn’t have access to any calendar yet.

To solve this, we have one additional step left.  Make sure you gave the following scope: https://www.googleapis.com/auth/calendar to your service account, as outlined in: https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthorit. If you are inside a google corporate domain, you’ll need to ask your system admin.

A Service Account is not much more than a standard User Account which has been pre-authenticated, so it can be used to access resources as it was a real user. This means that every time you make a request using a service account, you’re actually impersonating a real user. To achieve that we add to our script:

@client.sub = "yourgoogle@emailaccount.com"

Where the email address is the account you used to create the service account key.

Here’s a final version:

# calendar.rb
require "dotenv"
require "googleauth"
require "google/apis/calendar_v3"
Dotenv.load

class GoogleAPIClient
  attr_accessor :name

  def initialize
    scopes = [
      "https://www.googleapis.com/auth/calendar",
    ]

    # it initializes the connection, using the credentials set from ENV
    @client = ::Google::Auth.get_application_default(scopes)
    @client.sub = "youremailaddress@yourdomain.com"
    @service = Google::Apis::CalendarV3::CalendarService.new

    # it sets the authentication method and if fetches access token
    @service.authorization = @client
    @service.authorization.fetch_access_token!
  end

  def events(calendar_id)
    @service.list_events(calendar_id)
  end

  def calendars
    @service.list_calendar_lists
  end
end

calendar = GoogleAPIClient.new

puts calendar.calendars
puts calendar.events("yourcalendarid@resource.calendar.google.com")

And here you go!

TODO: add a screenshot of sort for output?

Topics

  • Web services

Categories

  • mynewsdesk
  • dashboards
  • ruby
  • google api