9

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:

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>");
  }
}
mcohen75
  • 690
  • 1
  • 8
  • 16
  • 1
    400 very often means a syntax error in your HTTP requests. I recommend you actually look at the HTTP requests/responses going back and forth. If you don’t see what's wrong, post them here. – Tim Bray Sep 18 '13 at 17:10
  • Thanks Tim. I've added both the request and response above. I continue to look at this and hope that the invalid_grant error will shed some light on this. – mcohen75 Sep 19 '13 at 11:02
  • Take a look at https://stackoverflow.com/questions/31840686/is-it-possible-to-use-json-key-instead-of-p12-key-for-service-account-credential – Andrey Belykh Oct 16 '17 at 15:04

3 Answers3

2

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#

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
1

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.

peleyal
  • 3,472
  • 1
  • 14
  • 25
  • We are building an integration for companies using Google Apps where we would like an administrator to setup access for all users. A user flow whereby the admin authorized access for the domain would work, but it doesn't look like this is possible. I have not found official word in any sort of documentation that states whether or not service accounts are supported for the calendar scopes. However, I have seen a number of posts indicating that users do have this working. An example on SO: http://stackoverflow.com/questions/18457241/google-oauth-2-0-service-account-calendar-api-php-client – mcohen75 Sep 21 '13 at 00:56
  • 1
    Also, that sample doesn't appear to actually get an oAuth token. It will run successfully if you don't specify the IAuthenticator instance when creating the service. When I do specify the IAuthenticator instance, no call is made to get a token (I verified with Fiddler). – mcohen75 Sep 21 '13 at 01:21
  • I have confirmed that calendar access is possible with a Service Account. Please see updates to my answer above. – mcohen75 Sep 21 '13 at 15:23
  • Did you try to run the plus samples I mentioned with the same code? Did it work? I wasn't aware that Calendar supports service account. By the way in our next release we going to get rid from DotNetOpenAuth and add our own implementation of OAuth2, ServiceAccount, JWT, etc. – peleyal Sep 21 '13 at 18:48
  • 1
    As I mentioned above, the sample doesn't actually get an OAuth Token. It actually works even if I don't set the IAuthenticator instance. – mcohen75 Sep 21 '13 at 18:51
  • What do you mean? If you remove line 74(https://code.google.com/p/google-api-dotnet-client/source/browse/Plus.ServiceAccount/Program.cs?repo=samples#74) the sample works as well? – peleyal Sep 22 '13 at 22:37
  • Yes, and if I leave that line of code in there is no request made to the oAuth endpoint to create the token. – mcohen75 Sep 23 '13 at 01:05
  • I just ran the Plus.ServiceAccount sample a minute ago. When I commented line74 I got the following error: An error has occured: Google.Apis.Requests.RequestError Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup. [40 3] Errors [ Message[Daily Limit for Unauthenticated Use Exceeded. Continued use requ ires signup.] Location[ - ] Reason[dailyLimitExceededUnreg] Domain[usageLimits] ] – peleyal Oct 02 '13 at 12:55
  • And without commenting line74 and by providing the right key (which I downloaded and replaced the current key.p12 file) it works. – peleyal Oct 02 '13 at 12:56
  • Using the Nuget package? Which version were you using? – mcohen75 Oct 02 '13 at 15:41
  • You can download the latest version (https://www.nuget.org/packages/Google.Apis.Plus.v1/1.5.0.105-beta) and don't forget also https://www.nuget.org/packages/Google.Apis.Authentication – peleyal Oct 02 '13 at 16:01
  • That link doesn't work. It looks like it only works if you're signed in. – mcohen75 Oct 02 '13 at 18:29
  • My bad sorry. Be aware that in the next weeks we are going to improve significantly the OAuth2 flows, You should follow our announcement blog - http://google-api-dotnet-client.blogspot.com/ to get details as soon as I publish them – peleyal Oct 02 '13 at 18:54
0

I suggest using a ServiceAccountCredential as in the answer that DaImTo gave, but you can specify the user's e-mail programatically. You don't have to go into their google calendar settings:

string serviceAccountEmail = "mail@project.iam.gserviceaccount.com";

// Read certificate from PKCS#12 file downloaded from the Google Developer Console
var certificate = new X509Certificate2(@"files/projectKey.p12", "notasecret",
    X509KeyStorageFlags.Exportable);

// Create Service Account Credential to add to specific user's calendar
ServiceAccountCredential credential = new ServiceAccountCredential(
    new ServiceAccountCredential.Initializer(serviceAccountEmail)
    {
        User = userEmail,
        Scopes = new[] { CalendarService.Scope.Calendar } // Manage user's calendars
    }.FromCertificate(certificate));
}

// Initialize service using credential
CalendarService service = new CalendarService(new BaseClientService.Initializer()
{
      HttpClientInitializer = credential,
      ApplicationName = "project"
});

This worked for my application once I'd created the Service Credential for my project in the Google Developers Console and enabled the Google Calendars API- I was able to create/read/update/delete events in users' calendars.

Community
  • 1
  • 1
tessafyi
  • 2,273
  • 2
  • 19
  • 28