Google Calendar API integration With .Net Core

Introduction

I'm going to explain how web server applications use Google OAuth 2.0 endpoints to implement OAuth 2.0 authorization to access Google APIs.

OAuth 2.0

OAuth 2.0 enables users to grant selective data-sharing permission to the application to applications while maintaining their personal credentials. As an illustration, an application can use OAuth 2.0 to obtain user consent for creating calendar events within their Google Calendar. 

Pre-Requisite

  • Enable Google API project.
  • Create Authorization credentials.
  • Get Access and Refresh the token on behalf of the authorization code.
  • Redirect URL: Google OAuth 2.0 server authenticates the user and obtains consent from the user for your application to access the requested Scope. The response is sent back to the application using the 'Redirect URL' you specified. 

Tokens

Access tokens have a limited lifespan and may become invalid over time. To prevent disruptions in accessing related APIs, you can refresh an access token without requiring user permission. This can be done by obtaining offline access when requesting the token.

  • Authorization Code: 1024 bytes
  • Access Token: 2048 bytes
  • Refresh Token: 512 bytes

Code Structure

Code Structure

Packages

  1. NodeTime
  2. Google.Apis
  3. Google.Apis.Auth
  4. Google.Apis.Calendar.v3

UserController

public class UserController: Controller {

  private IGoogleCalendarService _googleCalendarService;
  public UserController(IGoogleCalendarService googleCalendarService)

  {
    _googleCalendarService = googleCalendarService;
  }

  [HttpGet]
  [Route("/user/index")]
  public IActionResult Index() {
    return View();
  }

  [HttpGet]
  [Route("/auth/google")]
  public async Task < IActionResult > GoogleAuth() {
    return Redirect(_googleCalendarService.GetAuthCode());
  }

  [HttpGet]
  [Route("/auth/callback")]
  public async Task < IActionResult > Callback() {
    string code = HttpContext.Request.Query["code"];
    string scope = HttpContext.Request.Query["scope"];

    //get token method
    var token = await _googleCalendarService.GetTokens(code);
    return Ok(token);
  }

  [HttpPost]
  [Route("/user/calendarevent")]
  public async Task < IActionResult > AddCalendarEvent([FromBody] GoogleCalendarReqDTO calendarEventReqDTO) {
    var data = _googleCalendarService.AddToGoogleCalendar(calendarEventReqDTO);
    return Ok(data);
  }
}

DTO

public class GoogleCalendarReqDTO {
  public string Summary {
    get;
    set;
  }

  public string Description {
    get;
    set;
  }

  public DateTime StartTime {
    get;
    set;
  }

  public DateTime EndTime {
    get;
    set;
  }

  public string CalendarId {
    get;
    set;
  }

  public string refreshToken {
    get;
    set;
  }
}

public class GoogleTokenResponse {
  public string access_type {
    get;
    set;
  }

  public long expires_in {
    get;
    set;
  }

  public string refresh_token {
    get;
    set;
  }

  public string scope {
    get;
    set;
  }

  public string token_type {
    get;
    set;
  }
}

IGoogleCalendarService

public interface IGoogleCalendarService {
  string GetAuthCode();

  Task < GoogleTokenResponse > GetTokens(string code);
  string AddToGoogleCalendar(GoogleCalendarReqDTO googleCalendarReqDTO);
}

GoogleCalendarService

public class GoogleCalendarService: IGoogleCalendarService {

  private readonly HttpClient _httpClient;
  
  public GoogleCalendarService() {
    _httpClient = new HttpClient();
  }

  public string GetAuthCode() {
    try {

      string scopeURL1 = "https://accounts.google.com/o/oauth2/auth?redirect_uri={0}&prompt={1}&response_type={2}&client_id={3}&scope={4}&access_type={5};
      var redirectURL = "https://localhost:7272/auth/callback;
      string prompt = "consent"
      string response_type = "code";
      string clientID = ".apps.googleusercontent.com";
      string scope = "https://www.googleapis.com/auth/calendar;
      string access_type = "offline";
      string redirect_uri_encode = Method.urlEncodeForGoogle(redirectURL);
      var mainURL = string.Format(scopeURL1, redirect_uri_encode, prompt, response_type, clientID, scope, access_type);

      return mainURL;
    } catch (Exception ex) {
      return ex.ToString();
    }
  }

  public async Task < GoogleTokenResponse > GetTokens(string code) {

    var clientId = "_____.apps.googleusercontent.com";
    string clientSecret = "_____";
    var redirectURL = "https://localhost:7272/auth/callback;
    var tokenEndpoint = "https://accounts.google.com/o/oauth2/token;
    var content = new StringContent($"code={code}&redirect_uri={Uri.EscapeDataString(redirectURL)}&client_id={clientId}&client_secret={clientSecret}&grant_type=authorization_code", Encoding.UTF8, "application/x-www-form-urlencoded");

    var response = await _httpClient.PostAsync(tokenEndpoint, content);
    var responseContent = await response.Content.ReadAsStringAsync();
    if (response.IsSuccessStatusCode) {
      var tokenResponse = Newtonsoft.Json.JsonConvert.DeserializeObject < GoogleTokenResponse > (responseContent);
      return tokenResponse;
    } else {
      // Handle the error case when authentication fails
      throw new Exception($"Failed to authenticate: {responseContent}");
    }
  }

  public string AddToGoogleCalendar(GoogleCalendarReqDTO googleCalendarReqDTO) {
    try {
      var token = new TokenResponse {
        RefreshToken = googleCalendarReqDTO.refreshToken
      };
      var credentials = new UserCredential(new GoogleAuthorizationCodeFlow(
        new GoogleAuthorizationCodeFlow.Initializer {
          ClientSecrets = new ClientSecrets {
            ClientId = "___.apps.googleusercontent.com", ClientSecret = "__"
          }

        }), "user", token);

      var service = new CalendarService(new BaseClientService.Initializer() {
        HttpClientInitializer = credentials,
      });

      Event newEvent = new Event() {
        Summary = googleCalendarReqDTO.Summary,
          Description = googleCalendarReqDTO.Description,
          Start = new EventDateTime() {
            DateTime = googleCalendarReqDTO.StartTime,
              //TimeZone = Method.WindowsToIana();    //user's time zone
          },
          End = new EventDateTime() {
            DateTime = googleCalendarReqDTO.EndTime,
              //TimeZone = Method.WindowsToIana();    //user's time zone
          },
          Reminders = new Event.RemindersData() {
            UseDefault = false,
              Overrides = new EventReminder[] {

                new EventReminder() {
                    Method = "email", Minutes = 30
                  },

                  new EventReminder() {
                    Method = "popup", Minutes = 15
                  },

                  new EventReminder() {
                    Method = "popup", Minutes = 1
                  },
              }
          }

      };

      EventsResource.InsertRequest insertRequest = service.Events.Insert(newEvent, googleCalendarReqDTO.CalendarId);
      Event createdEvent = insertRequest.Execute();
      return createdEvent.Id;
    } catch (Exception e) {
      Console.WriteLine(e);
      return string.Empty;
    }
  }
}

Method.cs

public static class Method {
  public static string urlEncodeForGoogle(string url) {
    string unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.~";
    StringBuilder result = new StringBuilder();
    foreach(char symbol in url) {
      if (unreservedChars.IndexOf(symbol) != -1) {
        result.Append(symbol);
      } else {
        result.Append("%" + ((int) symbol).ToString("X2"));
      }
    }

    return result.ToString();

  }

  public static string WindowsToIana(string windowsTimeZoneId) {
    if (windowsTimeZoneId.Equals("UTC", StringComparison.Ordinal))
      return "Etc/UTC";

    var tzdbSource = TzdbDateTimeZoneSource.Default;
    var windowsMapping = tzdbSource.WindowsMapping.PrimaryMapping
      .FirstOrDefault(mapping => mapping.Key.Equals(windowsTimeZoneId, StringComparison.OrdinalIgnoreCase));

    return windowsMapping.Value;
  }
}

Program.cs

builder.Services.AddScoped<IGoogleCalendarService, GoogleCalendarService>();

Run your application

Google Calendar API Intregration with .NET Core

Google Calendar API Intregration with .NET Core

It will redirect to [Route("/auth/google")].

Google Calendar API Intregration with .NET Core

After successful sign-in, you will see this window because our calendar project on GCP is not recognized with the domain name. You can visit the google drive link, as I mentioned above.

Google Calendar API Intregration with .NET Core

Google Calendar API Intregration with .NET Core

Google will ask you to give access to your Google calendar, then continue.

Google Calendar API Intregration with .NET Core

It will redirect to [Route("/auth/callback")], and you will get an authorization code.

Google Calendar API Intregration with .NET Core

Now you can copy the refresh token and send the request.

Save the refresh token for future use.

Google Calendar API Intregration with .NET Core


Similar Articles