Interacting with Google APIs (OAuth 2.0)
While investigating Google Calendar API and looking for some sample codes in Github, I ran into a very interesting problem with OAuth 2 implementation in ASP.NET project. Perhaps, my lack of understanding with regard to the subject of .NET identity system may have contributed to my dilemma, but the main problem was that I was not able to find good examples (or online articles) that describe step-by-step instructions on how to implement the OAuth 2 Google APIs authorization in .NET without using ASP.NET identity provider which basically employs Entity Framework code-first approach. By the way, the reason I did not want to use the .NET identity provider is that I have already implemented the user management piece using MongoDB in my project.
All I wanted to do was to find a series of steps to use Google APIs (OAuth 2) in my .NET application without persistent user manger. Since I could not find any articles that show the entire process in detail, I had no choice but to collect necessary information from various sources and put them together to make it work for my project. I thought it was a good idea to share this experience for those who need to embark on a similar task. To make this article more appealing and interesting, I am also going to implement a method that reads calendar events from Google calendar.
Before diving into the .NET implementation, let me share some steps to illustrate the OAuth 2 process in order to understand how Google authorization works. I think it will be helpful for those who are clueless at this point like I was when I started this journey.
The first step is to enable Google APIs. Although it was a simple step, I already ran into a problem with this process. The OAuth 2.0 flow requires Google+ API enabled (because of user email address access, I think) along with Calendar API even though my application only needs access to Google calendar API.
To enable APIs,
Now you need to create web application credentials in the API Console.
You have completed all the necessary steps to enable APIs and created web application credentials, and now, it's time to illustrate how the OAuth 2.0 flow works by directly accessing the endpoints. Obviously, you will be using .NET client which internally handles request/response parts, but I thought it would be helpful to show steps (before going into .NET client side) to understand the underlying authorization process.
In order to access APIs, you need to get an access token (which usually expires in 3600 seconds = 1 hour) from google oAuth2.0 service endpoint. You can simply open the browser and submit data with GET verb as shown below. Keep in mind that you need to replace cliend_id with your own google Client ID obtained during "Create client ID" step (see previous steps for detail).
https://accounts.google.com/o/oauth2/auth?access_type=offline&client_id=123456789999-abcdefghijklmn12345678opqr9876t3.apps.googleusercontent.com&scope=https://www.googleapis.com/auth/calendar+https://www.googleapis.com/auth/plus.me&response_type=code&redirect_uri=http://localhost:60000/signin-google&state=/profile&approval_prompt=force
Basically, this GET request redirects the user to the google authentication screen, and once successfully authenticated, the google oAuth endpoint calls back to the uri specified in redirect_uri parameter (in this case, localhost:60000/signin-google) which includes the authorization code. You need this code along with client secrete to obtain the access token. For example, you will receive the authorization code as shown below:
http://localhost:6000/signin-google?state=/profile&code=4/uZYZvMNAPOWB-NfwxcLMecCV4pMwKcGbjI5T1QElckY#
Now you have client secret and authorization code to request the authorization or access token to access google Calendar API. What you need to do is to simply post with following values. If you want to test it, you need to use a Postman or similar API testing tools.
https://accounts.google.com/o/oauth2/token
data payload:
code=4/uZYZvMNAPOWB-NfwxcLMecCV4pMwKcGbjI5T1QElckY#
client_id=123456789999-abcdefghijklmn12345678opqr9876t3.apps.googleusercontent.com
client_secret=oQcvbnKObO0NH4KonmKJN87
redirect_uri=urn:ietf:wg:oauth:2.0:oob
grant_type=authorization_code
Make sure to add data payload in the body. This endpoint returns access_token, token_type, expires_in, id_token and refresh_token values:
{
"access_token": "",
"token_type": "Bearer",
"expires_in": 3600,
"id_token": "",
"refresh_token": ""
}
The good news is that all these steps are handled by OWIN and Google API libraries. Let's get started with .NET implementation.
In order to configure the OWIN authentication to work with Google, you need to use NuGet console to add the following packages to your project:
Install-Package Google.Apis.Calendar.v3 -ProjectName YourWebApp
Install-Package Microsoft.Owin.Host.SystemWeb -ProjectName YourWebApp
Install-Package Microsoft.AspNet.Identity.Owin -ProjectName YourWebApp
Install-Package Microsoft.Owin.Security.Google -ProjectName YourWebApp
Then, you need to implement Configuration method in Startup class. Add OWIN Startup class to App_Start folder as shown below.
By adding "Microsoft.Owin.Host.SystemWeb" into your project, you can now run OWIN middleware components (OMCs) in the IIS integrated pipeline. In this example, I am going to use three middleware components by calling a) CookieAuthentication, b) UseExternalSignInCookie and c) UseGoogleAuthentication. By the way, there are a plenty of good articles describing how the OWIN cookie authentication middleware works; so, I don't think it's necessary to go into details, but it is worth mentioning that the OWIN cookie authentication is claims-aware which is an improvement over the previous Form authentication.
Let me go over the Startup class briefly. The first line, which defines Scopes array, shows that the CalendarService.Scope.Calendar scope is included in my application. It is necessary since I am going to implement a function to read google calendar events:
private static string[] Scopes = { "openid", "email", CalendarService.Scope.Calendar };
Next, the default FileDataStore that comes with Google API is used here to simplify the implementation of this example; however, you can always create a custom DataStore for your application by implementing IDataStore (In my personal project, I have created MongoDBDataStore). If you are testing this application within Visual Studio using IISExpress, your application's storage (FileDataStore) is located in your login profile directory (%APPDATA%\Roaming\Google.Apis.Auth):
private IDataStore dataStore = new FileDataStore(GoogleWebAuthorizationBroker.Folder);
Just keep in mind that the purpose of this storage is to save the access token information so that it can be used during Google Calendar API calls. I will cover this part later:
await dataStore.StoreAsync(userID, tokenResponse);
If you go to the previous section that explains how to obtain the access token using API testing tools such as Postman, you can see that the value of tokenResponse is exactly what the oauth2/token endpoints returns: https://accounts.google.com/o/oauth2/token
The UseCookieAuthentication middleware is in charge of issuing a cookie and then, validating the cookie on subsequent requests. The cookie contains the claims of the user's identity.
The UseExternalSignInCookie middleware is in charge of issuing a cookie with the external identity. In this example, the external cookie is used to remember the identity received from Google during the ExternalLoginCallback action. I will cover this later in detail.
Finally, the UseGoogleAuthentication middleware is used to allow google authentication. As you can see in the code above, I have to specify the ClientId and ClientSecret in AuthenticationOptions. Also, the offline access is requested so that my application can continuously access Google APIs on the user's behalf, even when the user is offline (signed off).
Now, it's time to create my own Account controller to handle the external authentication, in this case, google authentication. In order to focus on the topic of Google authentication, I am going to eliminate application-specific authentication pieces, thus consequently achieving a bare minimum implementation of external sign-in methods:
Here is the sequence of events during the authentication process.
Now, the fun part begins!!! Let me create a controller to access the Google calendar API.
/Calendar/MyGoogleEvents.cshtml
The Calendar controller is straightforward. As you can see above, MyGoogleEvents calls GetCredentialForApiAsync method to retrieve the credential information for HttpClientInitializer property of the CalendarService. Then, you can request the primary calendar events (ExecuteAsync) and pass them to the MyGoogleEvents view.
In GetCredentialforApiAsync method, you can see that this method needs authorization code flow, external identity information and access token (from FileDataStore) to get the Google user credential. I think this is self-explanatory; however, I want to emphasize that the persistent data storage for token (in this case, FileDataStore) is needed to obtain the Google user credential for Calendar API calls.
I think all the necessary steps are thoroughly described in this post to help you implement Google APIs in your .NET application. I hope you have enjoyed reading this post as much I have enjoyed writing it.
Happy Coding !!!
All I wanted to do was to find a series of steps to use Google APIs (OAuth 2) in my .NET application without persistent user manger. Since I could not find any articles that show the entire process in detail, I had no choice but to collect necessary information from various sources and put them together to make it work for my project. I thought it was a good idea to share this experience for those who need to embark on a similar task. To make this article more appealing and interesting, I am also going to implement a method that reads calendar events from Google calendar.
Before diving into the .NET implementation, let me share some steps to illustrate the OAuth 2 process in order to understand how Google authorization works. I think it will be helpful for those who are clueless at this point like I was when I started this journey.
The first step is to enable Google APIs. Although it was a simple step, I already ran into a problem with this process. The OAuth 2.0 flow requires Google+ API enabled (because of user email address access, I think) along with Calendar API even though my application only needs access to Google calendar API.
To enable APIs,
- Go to API Manager (aka, API Console) and click Library. Under Google Apps APIs, you can find Calendar API. Click on it.
- You will see the button (ENABLE) next to the title, Google Calendar API. Click on it to enable the Calendar API.
- Follow the same steps for Google+ API which is located under Social APIs category.
Now you need to create web application credentials in the API Console.
- Click Credentials and select OAuth client ID.
- Select Web application and type "My Google Calendar App" in Name field.
- Under Restrictions, you have to specify your application server IP. Since my application will be installed in my local IIS, I am going to use my local IP address. Keep in mind that it is okay to add localhost during your testing phase. I will use localhost:60000 for this example:
- Type http://localhost:60000 in Auhtorized Javascript origins.
- Then, type http://localhost:60000/signin-google in Authorized redirect URIs
- Click Create. You will be prompted with your Client ID and Client Secret. You can copy these values now or retrieve them later.
You have completed all the necessary steps to enable APIs and created web application credentials, and now, it's time to illustrate how the OAuth 2.0 flow works by directly accessing the endpoints. Obviously, you will be using .NET client which internally handles request/response parts, but I thought it would be helpful to show steps (before going into .NET client side) to understand the underlying authorization process.
In order to access APIs, you need to get an access token (which usually expires in 3600 seconds = 1 hour) from google oAuth2.0 service endpoint. You can simply open the browser and submit data with GET verb as shown below. Keep in mind that you need to replace cliend_id with your own google Client ID obtained during "Create client ID" step (see previous steps for detail).
https://accounts.google.com/o/oauth2/auth?access_type=offline&client_id=123456789999-abcdefghijklmn12345678opqr9876t3.apps.googleusercontent.com&scope=https://www.googleapis.com/auth/calendar+https://www.googleapis.com/auth/plus.me&response_type=code&redirect_uri=http://localhost:60000/signin-google&state=/profile&approval_prompt=force
Basically, this GET request redirects the user to the google authentication screen, and once successfully authenticated, the google oAuth endpoint calls back to the uri specified in redirect_uri parameter (in this case, localhost:60000/signin-google) which includes the authorization code. You need this code along with client secrete to obtain the access token. For example, you will receive the authorization code as shown below:
http://localhost:6000/signin-google?state=/profile&code=4/uZYZvMNAPOWB-NfwxcLMecCV4pMwKcGbjI5T1QElckY#
Now you have client secret and authorization code to request the authorization or access token to access google Calendar API. What you need to do is to simply post with following values. If you want to test it, you need to use a Postman or similar API testing tools.
https://accounts.google.com/o/oauth2/token
data payload:
code=4/uZYZvMNAPOWB-NfwxcLMecCV4pMwKcGbjI5T1QElckY#
client_id=123456789999-abcdefghijklmn12345678opqr9876t3.apps.googleusercontent.com
client_secret=oQcvbnKObO0NH4KonmKJN87
redirect_uri=urn:ietf:wg:oauth:2.0:oob
grant_type=authorization_code
Make sure to add data payload in the body. This endpoint returns access_token, token_type, expires_in, id_token and refresh_token values:
{
"access_token": "",
"token_type": "Bearer",
"expires_in": 3600,
"id_token": "",
"refresh_token": ""
}
The good news is that all these steps are handled by OWIN and Google API libraries. Let's get started with .NET implementation.
In order to configure the OWIN authentication to work with Google, you need to use NuGet console to add the following packages to your project:
Install-Package Google.Apis.Calendar.v3 -ProjectName YourWebApp
Install-Package Microsoft.Owin.Host.SystemWeb -ProjectName YourWebApp
Install-Package Microsoft.AspNet.Identity.Owin -ProjectName YourWebApp
Install-Package Microsoft.Owin.Security.Google -ProjectName YourWebApp
Then, you need to implement Configuration method in Startup class. Add OWIN Startup class to App_Start folder as shown below.
By adding "Microsoft.Owin.Host.SystemWeb" into your project, you can now run OWIN middleware components (OMCs) in the IIS integrated pipeline. In this example, I am going to use three middleware components by calling a) CookieAuthentication, b) UseExternalSignInCookie and c) UseGoogleAuthentication. By the way, there are a plenty of good articles describing how the OWIN cookie authentication middleware works; so, I don't think it's necessary to go into details, but it is worth mentioning that the OWIN cookie authentication is claims-aware which is an improvement over the previous Form authentication.
public class Startup { private static string[] Scopes = { "openid", "email", CalendarService.Scope.Calendar }; private IDataStore dataStore = new FileDataStore(GoogleWebAuthorizationBroker.Folder); public void Configuration(IAppBuilder app) { // Cookie Authentication - Active app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login") }); // External Cookie Authetnication - Passive app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); // Google Authentication var options = new GoogleOAuth2AuthenticationOptions { AccessType = "offline", ClientId = "123456789999-abcdefghijklmn12345678opqr9876t3.apps.googleusercontent.com", ClientSecret = "oQcvbnKObO0NH4KonmKJN87", Provider = new GoogleOAuth2AuthenticationProvider { OnAuthenticated = async context => { var userID = context.Id; context.Identity.AddClaim(new Claim("GoogleUserId", context.Id)); var tokenResponse = new TokenResponse() { AccessToken = context.AccessToken, RefreshToken = context.RefreshToken, ExpiresInSeconds = (long)context.ExpiresIn.Value.TotalSeconds, Issued = DateTime.Now, }; await dataStore.StoreAsync(userID, tokenResponse); }, }, }; foreach (var scope in Scopes) options.Scope.Add(scope); app.UseGoogleAuthentication(options); // For claim-based authentication, you need configure this for AntiForgeryToken() AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier; } }
Let me go over the Startup class briefly. The first line, which defines Scopes array, shows that the CalendarService.Scope.Calendar scope is included in my application. It is necessary since I am going to implement a function to read google calendar events:
private static string[] Scopes = { "openid", "email", CalendarService.Scope.Calendar };
Next, the default FileDataStore that comes with Google API is used here to simplify the implementation of this example; however, you can always create a custom DataStore for your application by implementing IDataStore (In my personal project, I have created MongoDBDataStore). If you are testing this application within Visual Studio using IISExpress, your application's storage (FileDataStore) is located in your login profile directory (%APPDATA%\Roaming\Google.Apis.Auth):
private IDataStore dataStore = new FileDataStore(GoogleWebAuthorizationBroker.Folder);
Just keep in mind that the purpose of this storage is to save the access token information so that it can be used during Google Calendar API calls. I will cover this part later:
await dataStore.StoreAsync(userID, tokenResponse);
If you go to the previous section that explains how to obtain the access token using API testing tools such as Postman, you can see that the value of tokenResponse is exactly what the oauth2/token endpoints returns: https://accounts.google.com/o/oauth2/token
The UseCookieAuthentication middleware is in charge of issuing a cookie and then, validating the cookie on subsequent requests. The cookie contains the claims of the user's identity.
The UseExternalSignInCookie middleware is in charge of issuing a cookie with the external identity. In this example, the external cookie is used to remember the identity received from Google during the ExternalLoginCallback action. I will cover this later in detail.
Finally, the UseGoogleAuthentication middleware is used to allow google authentication. As you can see in the code above, I have to specify the ClientId and ClientSecret in AuthenticationOptions. Also, the offline access is requested so that my application can continuously access Google APIs on the user's behalf, even when the user is offline (signed off).
Now, it's time to create my own Account controller to handle the external authentication, in this case, google authentication. In order to focus on the topic of Google authentication, I am going to eliminate application-specific authentication pieces, thus consequently achieving a bare minimum implementation of external sign-in methods:
public class AccountController : Controller { // GET: Account/Login [AllowAnonymous] public ActionResult Login() { return View(); } // GET: Account/Logoff [AllowAnonymous] public ActionResult Logoff() { AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie, DefaultAuthenticationTypes.ExternalCookie); return RedirectToAction("Index", "Home"); } // POST: Account/ExternalLogin [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public ActionResult ExternalLogin(string provider = "Google", string returnUrl = null) { return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { returnUrl = returnUrl })); } // GET: Account/ExternalLoginCallback [AllowAnonymous] public async Task<actionresult> ExternalLoginCallback(string returnUrl) { vargetExternalLoginInfoTask = AuthenticationManager.GetExternalLoginInfoAsync(); // [TO DO] // Add codes that do not rely on loginInfo obtained by GetExternalLoginInfoAsync() var loginInfo = await getExternalLoginInfoTask; if (loginInfo == null) return RedirectToAction("Login"); var providerKey = loginInfo.Login.ProviderKey; IdentitySignin(loginInfo, isPersisted: true); return Redirect(returnUrl); } // Sign in with google login information private void IdentitySignin(ExternalLoginInfo loginInfo, bool isPersisted = false) { var claims = new List <Claim>();
/Account/Login.cshtmlclaims.Add(new Claim(ClaimTypes.NameIdentifier, loginInfo.Email)); claims.Add(new Claim(ClaimTypes.Name, loginInfo.DefaultUserName)); claims.Add(new Claim("ProviderKey", loginInfo.Login.ProviderKey)); var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie); AuthenticationManager.SignIn(new AuthenticationProperties() { AllowRefresh = true, IsPersistent = isPersisted, ExpiresUtc = DateTime.UtcNow.AddHours(1) // The authentication cookie expires after 1 hour past }, identity); } /// To get OWIN Authentication Manager private IAuthenticationManager AuthenticationManager { get { return HttpContext.GetOwinContext().Authentication; } } /// ChallegeResult Class private const string UserIdKey = "mY@WlnDem0"; public class ChallengeResult : HttpUnauthorizedResult { public string LoginProvider { get; set; } public string RedirectUri { get; set; } public string UserId { get; set; } public ChallengeResult(string provider) : this(provider, null) { } public ChallengeResult(string provider, string redirectUri) : this(provider, redirectUri, null) { } public ChallengeResult(string provider, string redirectUri, string userId) { LoginProvider = provider; RedirectUri = redirectUri; UserId = userId; } public override void ExecuteResult(ControllerContext context) { var properties = new AuthenticationProperties { RedirectUri = RedirectUri }; if(UserId != null) { properties.Dictionary[UserIdKey] = UserId; } var owin = context.HttpContext.GetOwinContext(); owin.Authentication.Challenge(properties, LoginProvider); } } }
@{ ViewBag.Title = "Login"; } <h2>Login</h2> <div class="row"> <div class="col-md-4"> <section id="socialLoginForm"> @using (Html.BeginForm("ExternalLogin", "Account")) { @Html.AntiForgeryToken() <div> <input type="hidden" id="provider" name="provider" value="Google" /> <input type="hidden" id="returnUrl" name="returnUrl" value="/Calendar/MyGoogleEvents" /> <button type="submit" class="btn btn-default" title="Log in using your google account">Google</button> </div> } </section> </div> </div>
Here is the sequence of events during the authentication process.
- First, you go to Login page where you will be presented with a form for ExternalLogin action. When a user clicks on the submit button (Google), ExternalLogin method will be invoked to start the Google authentication.
- Subsequently, this ExternalLogin method invokes ChallengeResult method which returns the ExecuteResult of the OWIN authentication challenge. By the way, ChallengeResult class extends HttpUnauthorizedResult. I am overriding ExecuteResult (ChallegeResult class) to provide the redirect url, which is the callback function. The callback function, ExternalLoginCallback waits for the external login information.
var getExternalLoginInfoTask = AuthenticationManager.GetExternalLoginInfoAsync();
var loginInfo = await getExternalLoginInfoTask; - Once authenticated with Google account with offline access granted, OnAuthenticated event handler (in Startup class - see previous section) will create a tokenResponse and save it to the local storage (FileDataStore) for future use (e.g., to access Google Calendar API). Then, the callback function resumes a suspended process and continues with IdentitySignin as shown above. I don't think I have to go through each step since the code is self-explanatory.
- Once completed, it redirects to /Calendar/MyGoogleEvents as specified in returnUrl.
Now, the fun part begins!!! Let me create a controller to access the Google calendar API.
public class CalendarController : Controller { private static string[] Scopes = { CalendarService.Scope.Calendar }; private static string ApplicationName = "Google Calendar API .NET"; private readonly IDataStore dataStore = new FileDataStore(GoogleWebAuthorizationBroker.Folder); private async Task<UserCredential> GetCredentialForApiAsync() { var initializer = new GoogleAuthorizationCodeFlow.Initializer { ClientSecrets = new ClientSecrets { ClientId = "123456789999-abcdefghijklmn12345678opqr9876t3.apps.googleusercontent.com", ClientSecret = "oQcvbnKObO0NH4KonmKJN87", }, Scopes = Scopes }; var flow = new GoogleAuthorizationCodeFlow(initializer); var identity = await AuthenticationManager.GetExternalIdentityAsync( DefaultAuthenticationTypes.ApplicationCookie); var userId = identity.FindFirstValue("ProviderKey"); var token = await dataStore.GetAsync(userId); return new UserCredential(flow, userId, token); } public async Task<ActionResult> MyGoogleEvents() { var getCredentialForApiAsync = GetCredentialForApiAsync(); // [TO DO] // Add codes that do not rely on credentials obtained by GetCredentialForApiAsync() var credentials = await getCredentialForApiAsync; var service = new CalendarService(new BaseClientService.Initializer() { HttpClientInitializer = credentials, ApplicationName = ApplicationName, }); // Get 10 calendar events from my primary google calendar EventsResource.ListRequest request = service.Events.List("primary"); request.TimeMin = DateTime.Now; request.ShowDeleted = false; request.SingleEvents = true; request.MaxResults = 10; request.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime; Events events = await request.ExecuteAsync(); return View(events.Items); } /// To get OWIN Authentication Manager private IAuthenticationManager AuthenticationManager { get { return HttpContext.GetOwinContext().Authentication; } } }
/Calendar/MyGoogleEvents.cshtml
@model IEnumerable<Google.Apis.Calendar.v3.Data.Event> @{ ViewBag.Title = "MyGoogleEvents"; } <h2>My Google Events</h2> <div class="row"> <div class="col-md-12"> <ul> @foreach (var item in Model) { <li>@item.Summary (@item.Description)</li> } </ul> </div> </div>
The Calendar controller is straightforward. As you can see above, MyGoogleEvents calls GetCredentialForApiAsync method to retrieve the credential information for HttpClientInitializer property of the CalendarService. Then, you can request the primary calendar events (ExecuteAsync) and pass them to the MyGoogleEvents view.
In GetCredentialforApiAsync method, you can see that this method needs authorization code flow, external identity information and access token (from FileDataStore) to get the Google user credential. I think this is self-explanatory; however, I want to emphasize that the persistent data storage for token (in this case, FileDataStore) is needed to obtain the Google user credential for Calendar API calls.
I think all the necessary steps are thoroughly described in this post to help you implement Google APIs in your .NET application. I hope you have enjoyed reading this post as much I have enjoyed writing it.
Happy Coding !!!
Interacting with Google API presents an avenue for limitless possibilities. These APIs offer a robust foundation for developers to integrate powerful functionalities into applications, spanning from maps and machine learning to cloud services. Leveraging Google APIs ensures seamless access to a wealth of data and services, streamlining processes and enhancing user experiences. Their extensive documentation and versatile SDKs simplify integration, empowering developers to innovate across diverse domains. Embracing Google APIs not only amplifies application capabilities but also exemplifies the potential of harnessing cutting-edge technology in today's digital landscape.
ReplyDelete