“Login Required” 401 Unauthorized message when calling the v3 Google Calendar API using a Service Account via OAuth 2.0

google-api-java-clientgoogle-calendar-apigoogle-oauthoauth-2.0

First, let me explain what I am trying to do, as this is a two part question.

I am building a JAX-RS service that internally authenticates with a Google account via OAuth2, so it can access and manipulate a Google calendar. This service will be called by a web site (Joomla), which will allow users that login into the site to register their email address with calendar events so they can get email notifications when the events are near. These users have no knowledge of the underlying Google account.

So my first question, is using Service Account authentication the right thing? Looking at the Google docs, its the only use case that caters for non-human authentication (so server to server authentication).

The second problem is that I have written some code to do this, but I get back a 401/Unauthorized response back the following error back when I call the execute method the Calendar feed. This code was lifted from the Google samples.

I am using v3-rev3-1.5.0-beta of the the Google Calendar API. I have turned on the Calendar API in the account, and created and downloaded a private key, that is used in the code below.

I am calling the code below in Eclipse locally, not from a web server.

So the first call I make is to get back a credential object (Id's obfuscated with X's):

private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
private static final JsonFactory JSON_FACTORY = new JacksonFactory();
.
.
.
GoogleCredential credential = new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
            .setJsonFactory(JSON_FACTORY)
            .setServiceAccountId("284XXXXXXXX@developer.gserviceaccount.com")
            .setServiceAccountScopes(CalendarScopes.CALENDAR)
            .setServiceAccountPrivateKeyFromP12File(new File("D:/3cd8XXXXXXXXXXXXXXXXXXXXXXXXXXXXXd635e-privatekey.p12"))
            .build();

The next step is to then call the Calendar API, like so (no other code called in between):

 Calendar calendar = Calendar.builder(HTTP_TRANSPORT, JSON_FACTORY)
        .setApplicationName("TestApp-Calendar/1.0").setHttpRequestInitializer(credential)
        .build();

 CalendarList feed = calendar.calendarList().list().execute();

The call to list().execute() results in the following error:

com.google.api.client.googleapis.json.GoogleJsonResponseException: 401 Unauthorized
{
"code" : 401,
"errors" : [ {
"domain" : "global",
"location" : "Authorization",
"locationType" : "header",
"message" : "Login Required",
"reason" : "required"
} ],
"message" : "Login Required"
}
at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:159)
at com.google.api.client.googleapis.json.GoogleJsonResponseException.execute(GoogleJsonResponseException.java:187)
at com.google.api.client.googleapis.services.GoogleClient.executeUnparsed(GoogleClient.java:115)
at com.google.api.client.http.json.JsonHttpRequest.executeUnparsed(JsonHttpRequest.java:112)
at com.google.api.services.calendar.Calendar$CalendarList$List.execute(Calendar.java:510)
at uk.org.reigatepriorybowmen.Service.registerEmailForAllCalendarEvents(Service.java:55)
at uk.org.reigatepriorybowmen.Service.main(Service.java:74)

The service account is setup as follows:

enter image description here

So, what am I doing wrong?! I have spent hours Googling around looking for solutions so this is my last hope.

Many thanks in advance for your help.

Justin

Best Solution

As for the 401, it seems some oddity with Google's API. I find the first time I run my app in a while, and then at seemingly random intervals hours spread apart, I always get a 401. On the second or third round it actually logs in.

The behavior is even accounted for in Google's Task API sample code here.

Notice this code:

void onRequestCompleted() {
received401 = false;
}

void handleGoogleException(IOException e) {
if (e instanceof GoogleJsonResponseException) {
  GoogleJsonResponseException exception = (GoogleJsonResponseException) e;
  if (exception.getStatusCode() == 401 && !received401) {
    received401 = true;
    accountManager.invalidateAuthToken(credential.getAccessToken());
    credential.setAccessToken(null);
    SharedPreferences.Editor editor2 = settings.edit();
    editor2.remove(PREF_AUTH_TOKEN);
    editor2.commit();
    gotAccount();
    return;
  }
}
Log.e(TAG, e.getMessage(), e);

}

In Google's own code sample they have made provision for a first time 401. It might be some special security issue that needs to be handled in code.

Related Question