I will explain how web server applications implement OAuth 2.0 authorization to access Google APIs using Google OAuth 2.0 endpoints.
OAuth 2.0
OAuth 2.0 enables users to grant selective data-sharing permission to applications while preserving their own credentials. As an example, an application can use OAuth 2.0 to obtain the user's permission to create calendar events in their Google Calendar.
Pre-Requisite
Enable Google API project.
Create credentials for Authorization.
Get Access and Refresh the token in the authorization code's name.
Redirect URL: The Google OAuth 2.0 server verifies the user's identity and obtains the user's permission to grant your application access to the requested Scope. Using the 'Redirect URL' you specified, the response is sent back to the application.
Tokens
Tokens of access have a finite lifespan and may expire over time. To prevent interruptions in access to related APIs, it is possible to refresh an access token without user permission. This is possible by requesting the token with inactive access.
1024 bytes are used for the Authorization Code
2048-bit Access Token
Refresh Token: 512 bytes
Code Structure
Packages
- NodeTime
- Google.Apis
- Google.Apis.Auth
- 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
It will redirect to [Route("/auth/google")].
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 will ask you to give access to your Google calendar, then continue.
It will redirect to [Route("/auth/callback")], and you will get an authorization code.
Now you can copy the refresh token and send the request.
Save the refresh token for future use.