0

I need to connect a Google Calendar on my .NET 4.5 application (VS 2013 project). I want to get all the information from the Calendar, such as: events, dates, notes, names, guests, etc...

I used the Google Developer Console to create both a Web Application Client ID and a Service Account, but I get different errors and no results. I've implemented 2 different methods, one to login with Web Application Client ID and one to use Service Account.

This is the common ASPX page

public partial class Calendar : System.Web.UI.Page
{
    // client_secrets.json path.
    private readonly string GoogleOAuth2JsonPath = ConfigurationManager.AppSettings["GoogleOAuth2JsonPath"];

    // p12 certificate path.
    private readonly string GoogleOAuth2CertificatePath = ConfigurationManager.AppSettings["GoogleOAuth2CertificatePath"];

    // @developer... e-mail address.
    private readonly string GoogleOAuth2EmailAddress = ConfigurationManager.AppSettings["GoogleOAuth2EmailAddress"];

    // certificate password ("notasecret").
    private readonly string GoogleOAuth2PrivateKey = ConfigurationManager.AppSettings["GoogleOAuth2PrivateKey"];

    // my Google account e-mail address.
    private readonly string GoogleAccount = ConfigurationManager.AppSettings["GoogleAccount"]; 

    protected void Page_Load(object sender, EventArgs e)
    {
        // Enabled one at a time to test
        //GoogleLoginWithServiceAccount();
        GoogleLoginWithWebApplicationClientId();
    }
}

Using Web Application Client ID

I've tried to configure the redirect URIs parameter for the JSON config file, but no URI seems to work. I'm on development environment so I'm using IIS Express on port 44300 (SSL enabled). The error I'm getting is:

Error: redirect_uri_mismatch
Application: CalendarTest
The redirect URI in the request: http://localhost:56549/authorize/ did not match a registered redirect URI.
Request details
scope=https://www.googleapis.com/auth/calendar
response_type=code
redirect_uri=http://localhost:56549/authorize/
access_type=offline
client_id=....apps.googleusercontent

The code

private void GoogleLoginWithWebApplicationClientId()
{
    UserCredential credential;

    // This example uses the client_secrets.json file for authorization.
    // This file can be downloaded from the Google Developers Console
    // project.
    using (FileStream json = new FileStream(Server.MapPath(GoogleOAuth2JsonPath), FileMode.Open,
        FileAccess.Read))
    {
        credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
            GoogleClientSecrets.Load(json).Secrets,
            new[] { CalendarService.Scope.Calendar },
            "...@developer.gserviceaccount.com", CancellationToken.None,
            new FileDataStore("Calendar.Auth.Store")).Result;
    }

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

    try
    {
        CalendarListResource.ListRequest listRequest = service.CalendarList.List();
        IList<CalendarListEntry> calendarList = listRequest.Execute().Items;

        foreach (CalendarListEntry entry in calendarList)
        {
            txtCalendarList.Text += "[" + entry.Summary + ". Location: " + entry.Location + ", TimeZone: " +
                                    entry.TimeZone + "] ";
        }
    }
    catch (TokenResponseException tre)
    {
        txtCalendarList.Text = tre.Message;
    }
}

Using Service Account (preferred)

I can reach the CalendarListResource.ListRequest listRequest = service.CalendarList.List(); line, so I guess the login works, but then, when I want the list on IList<CalendarListEntry> calendarList = listRequest.Execute().Items; I get the following error:

Error:"unauthorized_client", Description:"Unauthorized client or scope in request.", Uri:""

The code

private void GoogleLoginWithServiceAccount()
{
    /*
     * From https://developers.google.com/console/help/new/?hl=en_US#generatingoauth2:
     * The name of the downloaded private key is the key's thumbprint. When inspecting the key on your computer, or using the key in your application,
     * you need to provide the password "notasecret".
     * Note that while the password for all Google-issued private keys is the same (notasecret), each key is cryptographically unique.
     * GoogleOAuth2PrivateKey = "notasecret".
     */
    X509Certificate2 certificate = new X509Certificate2(Server.MapPath(GoogleOAuth2CertificatePath),
        GoogleOAuth2PrivateKey, X509KeyStorageFlags.Exportable);

    ServiceAccountCredential credential = new ServiceAccountCredential(
        new ServiceAccountCredential.Initializer(GoogleOAuth2EmailAddress)
        {
            User = GoogleAccount,
            Scopes = new[] { CalendarService.Scope.Calendar }
        }.FromCertificate(certificate));

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

    try
    {
        CalendarListResource.ListRequest listRequest = service.CalendarList.List();
        IList<CalendarListEntry> calendarList = listRequest.Execute().Items;

        foreach (CalendarListEntry entry in calendarList)
        {
            txtCalendarList.Text += "[" + entry.Summary + ". Location: " + entry.Location + ", TimeZone: " +
                                    entry.TimeZone + "] ";
        }
    }
    catch (TokenResponseException tre)
    {
        txtCalendarList.Text = tre.Message;
    }
}

I prefer the Service Account login because there's no need for user to login with consent screen, since the application should do it by itself each time it needs to refresh. Is it possible to use a Service Account with free Google Account or do I need Admin console? I've read many conflicting reports about that...

Anyway, looking around with Google and also in StackOverflow, I didn't find a solution. I've seen and tried many questions and solutions but with no results. Some examples:

Please help! :-)

UPDATE 1 - Using Service Account (preferred) - SOLVED!

The only problem in my code was:

ServiceAccountCredential credential = new ServiceAccountCredential(
    new ServiceAccountCredential.Initializer(GoogleOAuth2EmailAddress)
    {
        //User = GoogleAccount,
        Scopes = new[] { CalendarService.Scope.Calendar }
    }.FromCertificate(certificate));

There's NO NEED for User = GoogleAccount.

Community
  • 1
  • 1
Cheshire Cat
  • 1,941
  • 6
  • 36
  • 69
  • It's strange that your fix (UPDATE 1) is the remove the User. I am working on user lookups using the Google API (Scope = DirectoryService.Scope.AdminDirectoryUser) and the lookups threw an error and failed when I didn't specify a domain admin user email address for the User property in the credential Initializer. – Mark Apr 24 '15 at 15:33
  • It's the only thing I changed in my code and now it works. Maybe the approach it's different for each Scope... – Cheshire Cat Apr 27 '15 at 07:09

1 Answers1

2

There is definitely something wrong with your authentication. Here is a copy of my Service account authentication method.

 /// <summary>
        /// Authenticating to Google using a Service account
        /// Documentation: https://developers.google.com/accounts/docs/OAuth2#serviceaccount
        /// </summary>
        /// <param name="serviceAccountEmail">From Google Developer console https://console.developers.google.com</param>
        /// <param name="keyFilePath">Location of the Service account key file downloaded from Google Developer console https://console.developers.google.com</param>
        /// <returns></returns>
        public static CalendarService AuthenticateServiceAccount(string serviceAccountEmail, string keyFilePath)
        {

            // check the file exists
            if (!File.Exists(keyFilePath))
            {
                Console.WriteLine("An Error occurred - Key file does not exist");
                return null;
            }

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

            var certificate = new X509Certificate2(keyFilePath, "notasecret", X509KeyStorageFlags.Exportable);
            try
            {
                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",
                });
                return service;
            }
            catch (Exception ex)
            {

                Console.WriteLine(ex.InnerException);
                return null;

            }
        }

I have a tutorial on it as well. My tutorial Google Calendar API Authentication with C# The code above was ripped directly from my sample project Google-Dotnet-Samples project on GitHub

Note/headsup: Remember that a service account isn't you. It does now have any calendars when you start you need to create a calendar and insert it into the calendar list before you are going to get any results back. Also you wont be able to see this calendar though the web version of Google Calendar because you cant log in as a service account. Best work around for this is to have the service account grant you permissions to the calendar.

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • Thanks! Updated the question with solution for Service Account. I already added the ...@developer.gserviceaccount.com account to members of my calendars of my Google Account. So now, if I add an event from web Calendar Application can I see them? And vice-versa, when I add events from my .NET application, can I see them on the Calendar application? I need this! Because I also need to add that calendar to my smartphone using my Google Account. – Cheshire Cat Mar 11 '15 at 08:30
  • yes if you gave it access to your calendar it should be able to see it. Just make sure you gave it write access, so that it can update it. – Linda Lawton - DaImTo Mar 11 '15 at 08:31
  • 1) I've at least one calendar shared with ...@developer.gserviceaccount.com account, but when I reach `IList calendarList = listRequest.Execute().Items;` it is 0. 2) Do you also have any suggestion for my Web Application ID login method? – Cheshire Cat Mar 11 '15 at 08:36
  • check out my calendarlist method. https://github.com/LindaLawton/Google-Dotnet-Samples/blob/master/Google-Calendar/Google-Calendar-Api-dotnet/Google-Clndr-Api-dotnet/Google-Clndr-Api-dotnet/DaimtoCalendarListHelper.cs remember everthing needs to be sent against the CalendarService you created. service.CalendarList.List().execute(); – Linda Lawton - DaImTo Mar 11 '15 at 08:40
  • Great tutorial and code! I'm always in troubles with getting calendars and events... My list it's always empty. But I shared my calendar with @developer.gserviceaccount.com account (R/W) and also added an event from Calendar web application... – Cheshire Cat Mar 11 '15 at 10:10
  • there is a diffrece between calendarlist.list and calendars.get. Calendarlist is a list of calendars that the user has access to. On the website its that list on the left hand side. A user could be granted access to a calendar it will show up in calendars.get but it has to be added to the calendarlist. Don't ask me why they did it like that !!! – Linda Lawton - DaImTo Mar 11 '15 at 10:13
  • Using the `Get` method I get null entry and the shared calendar name is correct. The calendar id is the calendar name, right? – Cheshire Cat Mar 11 '15 at 10:51