Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I authenticate with the Google v3 api using a Service Account

I've followed the sample code here: https://code.google.com/p/google-api-dotnet-client/wiki/OAuth2#Service_Accounts

Authorization fails with: DotNetOpenAuth.Messaging.ProtocolException: Error occurred while sending a direct message or getting the response.

The inner exception is System.Net.WebException: The remote server returned an error: (400) Bad Request. The response body is empty and response URI is https://accounts.google.com/o/oauth2/token.

In the response below you will see that the specific error is invalid_grant.

Here is my code:

var certificate = new X509Certificate2(CertificatePath, "notasecret", X509KeyStorageFlags.Exportable);
var provider = new AssertionFlowClient(GoogleAuthenticationServer.Description, certificate)
    {
        ServiceAccountId = "<...>@developer.gserviceaccount.com",
        Scope = CalendarService.Scopes.Calendar.GetStringValue()
    };

var authenticator = new OAuth2Authenticator<AssertionFlowClient>(provider, AssertionFlowClient.GetState);

var calendarService =
    new CalendarService(new BaseClientService.Initializer()
        {
            Authenticator = authenticator
        });

var eventList = calendarService.Events.List("<id of the calendar>").Execute();

The certificate and ServiceAccountId are correct. I have triple checked and for good measure have regenerated the certificate. The Google Calendar API is turned on in the APIs console for the google developer account used to create the service account. This account is not part of the Google Apps domain.

I have also tested this with the ServiceAccountUser property of AssertionFlowClient specified. I now believe this to be required - in my successful testing of the CalendarService with a manually created JWT (see Manual Creation of OAuth Token Works below), I received a 404 error when attempting to create a token when the prn attribute is not included in the claim (i.e. ServiceAccountUser is not included).

Google Apps Domain Configuration

In the Google Apps domain I have granted access to the calendar for this service account.

Client Name: [snip].apps.googleusercontent.com

API Scopes:

  • Calendar (Read/Write) https://www.google.com/calendar/feeds/
  • Calendar (Read-Write) https://www.googleapis.com/auth/calendar

Installed NuGet packages

  • Google.Apis (1.5.0-beta)
  • Google.Apis.Calendar.v3 (1.5.0.59-beta)
  • Google.Apis.Authentication (1.5.0-beta)

Request and Response

POST https://accounts.google.com/o/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=utf-8
User-Agent: DotNetOpenAuth/4.0.0.11165
Host: accounts.google.com
Cache-Control: no-store,no-cache
Pragma: no-cache
Content-Length: 606
Connection: Keep-Alive

grant_type=assertion&assertion_type=http%3A%2F%2Foauth.net%2Fgrant_type%2Fjwt%2F1.0%2Fbearer&assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI5NzUzOTk3NzMyNi01NHFvMXY4OW5iZTk4dGNlbGIycWY0cDdjNThzYjhmMkBkZXZlbG9wZXIuZ3NlcnZpY2VhY2NvdW50LmNvbSIsInNjb3BlIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vYXV0aC9jYWxlbmRhciIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTM3OTU5MTA4MywiaWF0IjoxMzc5NTg3NDgzfQ.Ls_sv40MfB8MAD92JFcFiW5YYoRytQ3e2PA8RV_hn4FJfVHDo6uCSunN7950H2boO6LfX9EMrpjaf8ZyNyHyrQucQaWwfIFD6F2FpnqlcNkzXoqWMCwkt-k-8ypGMSZfFCEkhw8QOrlIPFZb6qx61689n08G9tZMTzHGYc2b8Gk

On closer inspection, the assertion appears correct, decoded here:

{"alg":"RS256","typ":"JWT"}{"iss":"97539977326-54qo1v89nbe98tcelb2qf4p7c58sb8f2@developer.gserviceaccount.com","scope":"https://www.googleapis.com/auth/calendar","aud":"https://accounts.google.com/o/oauth2/token","exp":1379591083,"iat":1379587483}

HTTP/1.1 400 Bad Request
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Date: Thu, 19 Sep 2013 10:44:42 GMT
Content-Type: application/json
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Server: GSE
Alternate-Protocol: 443:quic
Content-Length: 31

{
  "error" : "invalid_grant"
}

Manual Creation of OAuth Token Works

To confirm that I had things setup correctly, I created a token manually using google-oauth-jwt (here: https://github.com/extrabacon/google-oauth-jwt). I was able to successfully create a token using the same attributes I'm using with the code above. Once I created the token and use it in a custom IAuthenticator I can successfully retrieve events from a user's calendar in the target Google Apps domain. So, in case you're wondering, Calendar access is possible with Service Accounts!

Here's the IAuthenticator implementation, which simply adds the Authorization header:

public class Authenticator : IAuthenticator
{
  public void ApplyAuthenticationToRequest(System.Net.HttpWebRequest request)
  {
    request.Headers.Add(HttpRequestHeader.Authorization, "Bearer <token here>");
  }
}
like image 941
mcohen75 Avatar asked Sep 18 '13 10:09

mcohen75


2 Answers

I am not sure if something has changed since this question was asked but Google Calendar does in fact support service accounts.

When setting up a service account for use with Google Calendar API you only need to take the Service account email address. Go to the Google Calendar website. Find the Calendar Settings , then go to the Calendars tab, find the calendar you want to access and click on “Shared: Edit settings” add the service account email address like you would a persons email address. This will give the service account the same access as if you where sharing it with any other user.

string[] scopes = new string[] {
    CalendarService.Scope.Calendar, // Manage your calendars
    CalendarService.Scope.CalendarReadonly // View your Calendars
 };

 var certificate = new X509Certificate2(keyFilePath, "notasecret", X509KeyStorageFlags.Exportable);

ServiceAccountCredential credential = new ServiceAccountCredential(
    new ServiceAccountCredential.Initializer(serviceAccountEmail) {
        Scopes = scopes
    }.FromCertificate(certificate));

// Create the service.
    CalendarService service = new CalendarService(new BaseClientService.Initializer() {
        HttpClientInitializer = credential,
        ApplicationName = "Calendar API Sample",
    });

code ripped from Google Calendar API Authentication C#

like image 99
DaImTo Avatar answered Oct 30 '22 11:10

DaImTo


As I'm aware of Calendar service doesn't support Service Account.

You can check if the G+ sample works (https://code.google.com/p/google-api-dotnet-client/source/browse/Plus.ServiceAccount/Program.cs?repo=samples) with the same code you are trying to run here for the Calendar API. Google plus supports service account, so it should work.

Why can't you run OAuth2 user flow instead of service account?

UPDATE: After all, Calendar API support service account. Sorry for the confusion.

like image 23
peleyal Avatar answered Oct 30 '22 09:10

peleyal