Back to: ASP.NET Core Identity Tutorials
Integrating Google Authentication in ASP.NET Core MVC Application
In this article, I will discuss Integrating Google Authentication in ASP.NET Core MVC Applications using ASP.NET Core Identity. Please read our previous article discussing How to Create Google OAuth Credentials.
Integrating Google Authentication in ASP.NET Core MVC Application
We want to allow users to log in using local and external accounts like Google, Facebook, etc. Let us proceed with Google authentication. Here, we will set up the UI (i.e., the User Interface) and configure it to redirect the request to Google when the Sign-In with Google button is clicked. So, we want our login page to look like this:
When the user clicks on the Google button, it will open the following page asking the user to log in using his/her Google credentials as follows:
On successful login, it will make an entry into the AspNetUsers table without a password, as shown in the image below.
It will also store the provider details in the AspNetUserLogins table, as shown in the below image. The ProviderKey will be unique for each user.
Integrating Google Authentication in ASP.NET Core MVC using Identity
Integrating Google Authentication in an ASP.NET Core MVC application using ASP.NET Core Identity involves several steps. This process requires setting up the Google OAuth credentials and configuring your ASP.NET Core MVC application to use these credentials.
Create Google OAuth Credentials:
We have already created Google OAuth 2.0 credentials in the Google Developer Console. Please note down the Client ID and Client Secret generated for your application.
Install Necessary NuGet Packages:
Ensure that your ASP.NET Core MVC project has the necessary NuGet packages installed. You need to install Microsoft.AspNetCore.Authentication.Google package. So please install the Microsoft.AspNetCore.Authentication.Google package by executing the following command in the Package Manager Console.
Install-Package Microsoft.AspNetCore.Authentication.Google
Configure Google Authentication Services:
In the Program.cs file, configure the authentication services to include Google authentication. You need to use the Client ID and Client Secret obtained from Google. So, please add the following code to the Program class. Please use the actual values for [Your Google Client ID] and [Your Google Client Secret].
builder.Services.AddAuthentication() .AddGoogle(options => { options.ClientId = "[Your Google Client ID]"; options.ClientSecret = "[Your Google Client Secret]"; // Configure Other Options });
Understanding AddGoogle Method in ASP.NET Core
In ASP.NET Core, the AddGoogle method integrates Google authentication into our web application. The primary purpose of this method is to configure the middleware necessary for authenticating users via their Google accounts.
How AddGoogle Method Works?
When we call AddGoogle, it registers the Google authentication service with ASP.NET Core’s dependency injection system. This registration tells our application how to handle user sign-ins with Google.
The method requires a Client ID and Client Secret, which we obtain from the Google Developer Console. These credentials establish a trusted connection between our application and Google’s authentication servers.
You can also configure additional options in the AddGoogle method, such as the redirect URI, the path in your application where users will be redirected after successfully authenticating with Google.
Modifying Login View Model
The model for the login view is the LoginViewModel class. Please include ReturnUrl and ExternalLogins properties in this LoginViewModel class. So, modify the LoginViewModel.cs class file as follows:
using Microsoft.AspNetCore.Authentication; using System.ComponentModel.DataAnnotations; namespace ASPNETCoreIdentityDemo.Models { public class LoginViewModel { [Required] [EmailAddress] public string Email { get; set; } [Required] [DataType(DataType.Password)] public string Password { get; set; } [Display(Name = "Remember Me")] public bool RememberMe { get; set; } public string? ReturnUrl { get; set; } // AuthenticationScheme is in Microsoft.AspNetCore.Authentication namespace public IList<AuthenticationScheme>? ExternalLogins { get; set; } } }
Here,
- ReturnUrl is the URL the user was trying to access before authentication. We preserve and pass it between requests using the ReturnUrl property so the user can be redirected to that URL upon successful authentication.
- ExternalLogins property stores the list of external logins (like Facebook, Google, Microsoft, Twitter, etc) that are enabled in our application.
What is the AuthenticationScheme class in ASP.NET Core?
The AuthenticationScheme class in ASP.NET Core is an essential part of the authentication system, providing a way to configure different authentication methods within our application. Managing how users are authenticated and identified when external authentication is enabled is necessary.
Modifying HttpGet Login Action Method in AccountController
Populate ReturnUrl and ExternalLogins properties of LoginViewModel and then pass the instance to the view. So, please modify the HttpGet Login Action method of the Account Controller as follows:
[HttpGet] [AllowAnonymous] public async Task<IActionResult> Login(string? ReturnUrl = null) { LoginViewModel model = new LoginViewModel { ReturnUrl = ReturnUrl, //The GetExternalAuthenticationSchemesAsync() method of the SignInManager class is used to retrieve //a list of all external authentication schemes that have been configured in the application. ExternalLogins = (await signInManager.GetExternalAuthenticationSchemesAsync()).ToList() }; return View(model); }
Note: The GetExternalAuthenticationSchemesAsync() method is useful in scenarios where we want to offer users the option to sign in using external identity providers such as Google, Facebook, Twitter, Microsoft, etc. Currently, we only have one external identity provider configured, which is Google.
Modifying HTTP Post Login Action Method in AccountController
Next, modify the HttpPost Login Action method of the Account Controller as follows. In case of any error, we are populating the ExternalLogins property of LoginViewModel.
[HttpPost] public async Task<IActionResult> Login(LoginViewModel model, string? ReturnUrl) { if (ModelState.IsValid) { var result = await signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false); if (result.Succeeded) { if (!string.IsNullOrEmpty(ReturnUrl) && Url.IsLocalUrl(ReturnUrl)) { return Redirect(ReturnUrl); } // Handle successful login return RedirectToAction(nameof(HomeController.Index), "Home"); } if (result.RequiresTwoFactor) { // Handle two-factor authentication case } if (result.IsLockedOut) { // Handle lockout scenario } else { model.ExternalLogins = (await signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); // Handle failure ModelState.AddModelError(string.Empty, "Invalid login attempt."); return View(model); } } // If we got this far, something failed, redisplay form model.ExternalLogins = (await signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); return View(model); }
Modifying Login View (Login.cshtml)
Modify the Login.cshtml view as follows:
@model LoginViewModel <div class="row"> <div class="col-md-6"> <h1>Local Account Login</h1> <hr /> <form method="post" asp-action="Login" asp-controller="Account"> <input type="hidden" name="ReturnUrl" value="@Model.ReturnUrl" /> <div asp-validation-summary="All" class="text-danger"></div> <div class="form-group"> <label asp-for="Email"></label> <input asp-for="Email" class="form-control" /> <span asp-validation-for="Email" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Password"></label> <input asp-for="Password" class="form-control" /> <span asp-validation-for="Password" class="text-danger"></span> </div> <div class="form-group"> <div class="checkbox"> <label asp-for="RememberMe"> <input asp-for="RememberMe" /> @Html.DisplayNameFor(m => m.RememberMe) </label> </div> </div> <button type="submit" class="btn btn-primary">Login</button> </form> </div> <div class="col-md-6"> <h1>External Login</h1> <hr /> @if (Model.ExternalLogins == null || Model.ExternalLogins.Count == 0) { <div>No external logins configured</div> } else { <div> @foreach (var provider in Model.ExternalLogins) { <button type="button" class="btn btn-primary external-login-btn" data-provider="@provider.Name" data-url="@Url.Action("ExternalLogin", "Account", new { provider = provider.Name, returnUrl = Model.ReturnUrl })" title="Log in using your @provider.DisplayName account"> @provider.DisplayName </button> } </div> } </div> </div> @section Scripts { <script type="text/javascript"> document.querySelectorAll('.external-login-btn').forEach(button => { button.addEventListener('click', function () { var provider = this.getAttribute('data-provider'); var url = this.getAttribute('data-url'); // Define popup window size var width = 500; var height = 600; var left = (window.innerWidth / 2) - (width / 2); var top = (window.innerHeight / 2) - (height / 2); // Open the popup window var popup = window.open(url, provider, `width=${width},height=${height},top=${top},left=${left},resizable=yes`); // Monitor the popup to check if it closes var checkPopup = setInterval(function () { if (popup.closed) { clearInterval(checkPopup); // Optionally, reload or fetch updated user state location.reload(); // Refresh the parent window to reflect login state } }, 500); // Check every 500ms if the popup is closed }); }); </script> }
Creating HttpGet ExternalLogin Action Method in AccountController
Next, add the following HttpGet ExternalLogin Action Method in the Account Controller. The following ExternalLogin action method will redirect the user to the external login provider sign-in page, in this case, Google. Upon successful authentication, Google will redirect the user back to our application, and the ExternalLoginCallback action method will be executed.
[AllowAnonymous] [HttpGet] public IActionResult ExternalLogin(string provider, string returnUrl) { // Generate a URL for the "ExternalLoginCallback" action method in the "Account" controller. // This URL includes the returnUrl as a route parameter, which will be used to redirect the user // back to the original page they were trying to access after a successful external login. var redirectUrl = Url.Action( action: "ExternalLoginCallback", // The name of the callback action method. controller: "Account", // The name of the controller containing the callback method. values: new { ReturnUrl = returnUrl } // Pass the returnUrl as a parameter to the callback method. ); // Configure authentication properties for the external login. // The "ConfigureExternalAuthenticationProperties" method sets up parameters needed for the external provider, // such as the login provider name (e.g., Google, Facebook) and the redirect URL to be used after login. var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); // Redirect the user to the external provider's login page (e.g., Google or Facebook). // The "ChallengeResult" triggers the external authentication process, which redirects the user // to the external provider's login page using the configured properties. return new ChallengeResult(provider, properties); }
Creating ExternalLoginCallback Action Method in Account Controller:
Please add the following ExternalLoginCallback Action Method to the Account Controller. Upon successful authentication, Google redirects the user back to our application, and the following ExternalLoginCallback action is executed. The following method is self-explained, so please go through the comment line.
[AllowAnonymous] public async Task<IActionResult> ExternalLoginCallback(string? returnUrl, string? remoteError) { // If no returnUrl is provided, default to the application's home page. returnUrl = returnUrl ?? Url.Content("~/"); // Check if an error occurred during the external authentication process. // If so, display an alert to the user and close the popup window. if (remoteError != null) { return Content($"<script>alert('Error from external provider: {remoteError}'); window.close();</script>", "text/html"); } // Retrieve login information about the user from the external login provider (e.g., Google, Facebook). // This includes details like the provider's name and the user's identifier within that provider. var info = await signInManager.GetExternalLoginInfoAsync(); // If the login information could not be retrieved, display an error message // and close the popup window. if (info == null) { return Content($"<script>alert('Error loading external login information.'); window.close();</script>", "text/html"); } // Attempt to sign in the user using their external login details. // If a corresponding record exists in the AspNetUserLogins table, the user will be logged in. var signInResult = await signInManager.ExternalLoginSignInAsync( info.LoginProvider, // The name of the external login provider (e.g., Google, Facebook). info.ProviderKey, // The unique identifier of the user within the external provider. isPersistent: false, // Indicates whether the login session should persist across browser restarts. bypassTwoFactor: true // Bypass two-factor authentication if enabled. ); // If the external login succeeds, redirect the parent window to the returnUrl // and close the popup window. if (signInResult.Succeeded) { return Content($"<script>window.opener.location.href = '{returnUrl}'; window.close();</script>", "text/html"); } // If the user does not have a corresponding record in the AspNetUserLogins table, // attempt to create a new account using the user's email from the external provider. var email = info.Principal.FindFirstValue(ClaimTypes.Email); // Retrieve the user's email from the external login provider. if (email != null) { // Check if a local user account with the retrieved email already exists. var user = await userManager.FindByEmailAsync(email); // If no local account exists, create a new user in the AspNetUsers table. if (user == null) { user = new ApplicationUser { UserName = email, // Set the username to the user's email. Email = email, // Set the email. FirstName = info.Principal.FindFirstValue(ClaimTypes.GivenName), // Retrieve and set the user's first name. LastName = info.Principal.FindFirstValue(ClaimTypes.Surname) // Retrieve and set the user's last name. }; // Create the new user in the database. await userManager.CreateAsync(user); } // Link the external login to the newly created or existing user account. // This inserts a record into the AspNetUserLogins table. await userManager.AddLoginAsync(user, info); // Sign in the user locally after linking their external login. await signInManager.SignInAsync(user, isPersistent: false); // Redirect the parent window to the returnUrl and close the popup window. return Content($"<script>window.opener.location.href = '{returnUrl}'; window.close();</script>", "text/html"); } // If the email claim is not provided by the external login provider, // display an error message and close the popup window. return Content($"<script>alert('Email claim not received. Please contact support.'); window.close();</script>", "text/html"); }
Now, run the application and check the functionality; it should work as expected.
How Does Google OAuth Credentials Work?
Google OAuth 2.0 authorization allows users to securely grant your application access to their Google resources (like Gmail, Drive, etc.) without sharing their credentials. For a better understanding of the workflow of Google OAuth 2.0, please look at the below image:
Here’s a step-by-step explanation of how Google OAuth credentials work based on the diagram:
- User Requests Token (Your App): Your application sends a request to the Google OAuth 2.0 server to access the user’s data protected by Google. This request includes the client ID, scope (the resources that the application wants to access), and redirect URI, among other parameters.
- User Login and Consent (Google Servers): The user is prompted to log in to their Google account (if not already logged in) and asked to grant your application permission to access their data as specified by the scope. If the user agrees, the Google OAuth server will send an authorization code back to your application. This interaction happens through the user’s browser.
- Authorization Code (Google Servers to Your App): After the user logs in and consents, Google sends an authorization code back to your app. The authorization code is a short-lived (temporary) code that your application will use to get the actual access token.
- Exchange Code for Token (Your App): Your application sends a request to the Google OAuth server, including the authorization code, client ID, client secret, and redirect URI. This exchange is done server-to-server and is secure because it involves your client secret. This step is performed to exchange the authorization code for an actual access token.
- Token Response (Google Servers to Your App): Google verifies the authorization code, client ID, and client secret. If the verification is successful, Google responds with an access token (and optionally a refresh token). The access token is used to make API requests on behalf of the user. The refresh token can be used to get a new access token once the current one expires.
- Use Token to Call Google API (Your App): Finally, your application can use the access token to make API calls to Google services (e.g., Google Calendar, Gmail, etc.). The access token is included in the HTTP headers of your API requests. This allows your app to access the user’s Google data according to the permissions granted during login.
Putting it all together, the OAuth 2.0 process keeps the user’s login credentials secure, ensures they’ve explicitly granted permission for your app to access their data, and issues secure tokens that your app can use to act on the user’s behalf with Google services.
In the next article, I will discuss Microsoft Account External Login Setup. In this article, I explain how to integrate Google authentication in ASP.NET Core MVC. I hope you enjoy this article, Integrating Google Authentication in ASP.NET Core MVC.