Search code examples
asp.netgoogle-apigoogle-calendar-apigoogle-oauthgoogle-api-dotnet-client

Google Calendar API v3 .NET authentication with Service Account or Web Application ID


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 },
            "[email protected]", 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.


Solution

  • 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.