Step-by-Step Guide to Implementing Google External Login in ASP.NET Core
Introduction
In today's digital age, users expect quick and seamless login experiences. External login providers such as Google, Facebook, and Twitter enable users to sign in using their existing accounts, reducing the friction associated with creating a new account on your platform. This guide will walk you through the process of integrating Google External Login into an ASP.NET Core application.
Prerequisites
Before we begin, ensure you have the following prerequisites:
- .NET SDK 5.0 or later installed.
- Visual Studio or any code editor (e.g., Visual Studio Code, JetBrains Rider).
- Basic understanding of C# programming and ASP.NET Core principles.
Step 1: Create a New ASP.NET Core Web Application
- Start Visual Studio and choose
Create a new project
. - Select
ASP.NET Core Web App (Model-View-Controller)
and clickNext
. - Configure your project settings:
- Name the project
GoogleAuthDemo
. - Choose a location to store the project.
- Click
Create
.
- Name the project
- Select the ASP.NET Core version (preferably 5.0 or later).
- Check the
Authentication
option and set it toIndividual User Accounts
. - Click
Create
to generate a new MVC project.
Step 2: Register Your Application in Google APIs Console
Google requires you to register your application to obtain the necessary credentials for authentication.
- Go to the Google Developers Console.
- Create a new project by clicking on
Select a project
and thenNew Project
. - Name your project, for example,
GoogleAuthDemo
, and clickCreate
. - Once the project is created, navigate to
APIs & Services
>Credentials
. - Click
Create Credentials
, then selectOAuth client ID
. - Configure the consent screen if prompted:
- Fill in the
App name
. - Provide the
Support email
. - Save and continue.
- Fill in the
- Set up the OAuth Consent Screen:
- Select
External
orInternal
users as per your requirement. - Fill in the necessary information such as
App domain
,Authorized domains
, andApplication Home page
. - Save and continue.
- Select
- Configure the credentials:
- Choose
Web application
as the application type. - Name your credentials, e.g.,
GoogleAuthDemo
. - Add
Authorized redirect URIs
. This must match the callback URL for your application. For development purposes, you can usehttps://localhost:5001/signin-google
(adjust the port number accordingly). - Click
Create
.
- Choose
- Copy the
Client ID
andClient Secret
that are displayed. You will need these to configure your ASP.NET Core application.
Step 3: Configure Google External Authentication in ASP.NET Core
- Open your newly created ASP.NET Core project in Visual Studio.
- Open the
appsettings.json
file and add the Google client ID and client secret:
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-GoogleAuthDemo-879B329A-8955-406F-A372-8689B307D42A;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"Authentication": {
"Google": {
"ClientId": "YOUR_GOOGLE_CLIENT_ID",
"ClientSecret": "YOUR_GOOGLE_CLIENT_SECRET"
}
}
}
Replace YOUR_GOOGLE_CLIENT_ID
and YOUR_GOOGLE_CLIENT_SECRET
with the values obtained from the Google Developers Console.
Open
Startup.cs
orProgram.cs
depending on your ASP.NET Core version.- For ASP.NET Core 6.0 and later, you’ll be using
Program.cs
. For earlier versions like 3.x and 5.x, you'll be usingStartup.cs
. Instructions below cater to both versions.
- For ASP.NET Core 6.0 and later, you’ll be using
For ASP.NET Core 6.0 and later (
Program.cs
):
using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.Authentication.JwtBearer;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddControllersWithViews();
// Add Google Authentication
builder.Services.AddAuthentication()
.AddGoogle(options =>
{
IConfigurationSection googleAuthNSection =
builder.Configuration.GetSection("Authentication:Google");
options.ClientId = googleAuthNSection["ClientId"];
options.ClientSecret = googleAuthNSection["ClientSecret"];
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
For earlier versions of ASP.NET Core (using
Startup.cs
):- In
Startup.cs
, find theConfigureServices
method and add Google authentication.
- In
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
// Add Google Authentication
services.AddAuthentication().AddGoogle(options =>
{
IConfigurationSection googleAuthNSection =
Configuration.GetSection("Authentication:Google");
options.ClientId = googleAuthNSection["ClientId"];
options.ClientSecret = googleAuthNSection["ClientSecret"];
});
}
- For both versions: Ensure that
app.UseAuthentication()
andapp.UseAuthorization()
are called in theConfigure
method withinStartup.cs
orProgram.cs
.
Step 4: Update the Account Controller and Views
The default ASP.NET Core Identity setup already includes support for external logins. However, you need to update the views to include the Google login option.
Open the
Login.cshtml
view located inViews/Account
.Add the following code after the
Username/Password
form:
<h4>Use a third-party account to log in.</h4>
<hr />
<div>
<p>
@foreach (var provider in Model.ExternalLogins!)
{
<form asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
</form>
}
</p>
</div>
- Update the
Login.cshtml.cs
model to pass the external logins to the view:
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using GoogleAuthDemo.Data;
namespace GoogleAuthDemo.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class LoginModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly ILogger<LoginModel> _logger;
public LoginModel(SignInManager<IdentityUser> signInManager,
ILogger<LoginModel> logger,
UserManager<IdentityUser> userManager)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
[BindProperty]
public InputModel Input { get; set; } = new InputModel();
public IList<AuthenticationScheme> ExternalLogins { get; set; } = new List<AuthenticationScheme>();
public string ReturnUrl { get; set; } = string.Empty;
[TempData]
public string ErrorMessage { get; set; } = string.Empty;
public class InputModel
{
[Required]
[EmailAddress]
public string Email { get; set; } = string.Empty;
[Required]
[DataType(DataType.Password)]
public string Password { get; set; } = string.Empty;
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
public async Task OnGetAsync(string returnUrl = null)
{
if (!string.IsNullOrEmpty(ErrorMessage))
{
ModelState.AddModelError(string.Empty, ErrorMessage);
}
returnUrl ??= Url.Content("~/");
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
ReturnUrl = returnUrl;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
// If we got this far, something failed, redisplay form
return Page();
}
}
}
Step 5: Test the Google External Login
- Run your application. You can do this by pressing
F5
or clicking on theStart Debugging
button in Visual Studio. - Navigate to the
Login
page (https://localhost:5001/Identity/Account/Login
). - You should see an option to log in with Google.
- Click on the
Google
button. - You will be redirected to the Google login page. Enter your Google credentials.
- After successful authentication, you will be redirected back to your application and logged in.
Step 6: Handle External Login Registration
When a user logs in with an external provider, they might not have an account in your application. You can handle this by redirecting the user to a registration page to complete the registration process.
- Modify the
ExternalLogin
page model (Pages\Account\ExternalLogin.cshtml.cs
) to handle new user registrations:
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using GoogleAuthDemo.Data;
namespace GoogleAuthDemo.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ExternalLoginModel : PageModel
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
private readonly ILogger<ExternalLoginModel> _logger;
public ExternalLoginModel(
SignInManager<IdentityUser> signInManager,
UserManager<IdentityUser> userManager,
ILogger<ExternalLoginModel> logger)
{
_signInManager = signInManager;
_userManager = userManager;
_logger = logger;
}
[BindProperty]
public InputModel Input { get; set; } = new InputModel();
public string LoginProvider { get; set; } = string.Empty;
public string ReturnUrl { get; set; } = string.Empty;
[TempData]
public string ErrorMessage { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public class InputModel
{
[Required]
[EmailAddress]
public string Email { get; set; } = string.Empty;
}
public IActionResult OnGet()
{
return RedirectToPage("./Login");
}
public async Task<IActionResult> OnPost(string provider, string returnUrl = null)
{
// Request a redirect to the external login provider.
var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { area = "Identity", returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return new ChallengeResult(provider, properties);
}
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
_logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
return LocalRedirect(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToPage("./Lockout");
}
else
{
// If the user does not have an account, then ask the user to create an account.
ReturnUrl = returnUrl;
LoginProvider = info.LoginProvider;
Email = info.Principal.FindFirstValue("email");
Input.Email = Email;
return Page();
}
}
public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
if (ModelState.IsValid)
{
var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
return LocalRedirect(returnUrl);
}
}
ForEach(err => ModelState.AddModelError(string.Empty, err.Description));
}
LoginProvider = info.LoginProvider;
ReturnUrl = returnUrl;
return Page();
}
}
}
- Create the
ExternalLogin.cshtml
page (Pages\Account\ExternalLogin.cshtml
) to allow users to register after external login:
@page
@model ExternalLoginModel
@{
ViewData["Title"] = "Register";
}
<h2>@ViewData["Title"] your @Model.LoginProvider account.</h2>
<h4 id="external-login-title">Associate your @Model.LoginProvider account.</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<p id="external-login-description">
You've successfully authenticated with <strong>@Model.LoginProvider</strong>.
Please enter an email address for this site below and click the Register button to finish
logging in.
</p>
<div class="row">
<div class="col-md-4">
<form asp-page-handler="Confirmation" asp-route-returnUrl="@Model.ReturnUrl" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-floating">
<input asp-for="Input.Email" class="form-control" autocomplete="email" />
<label asp-for="Input.Email" class="form-label"></label>
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<button type="submit" class="w-100 btn btn-lg btn-primary">Register</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Step 7: Secure Your Application for Production
When deploying your application to production, ensure that you follow best practices:
- Use HTTPS: Ensure that your application is served over HTTPS to protect sensitive data, including Google Client ID and Client Secret.
- Validate Redirect URIs: Double-check the redirect URIs in the Google Developers Console to ensure they match your production URLs.
- Environment Variables: Store sensitive information such as Client ID and Client Secret in environment variables or secure vaults rather than hardcoding them in your configuration files.
- Rate Limiting and Throttling: Implement rate limiting to prevent abuse of your login system.
Conclusion
Congratulations! You’ve successfully integrated Google External Login into your ASP.NET Core application. This allows users to authenticate using their Google accounts, providing a seamless and efficient login experience. You can further customize the authentication flow by handling additional claims, integrating other external providers, and enhancing security measures.
Feel free to explore the ASP.NET Core documentation for more advanced scenarios and configurations. Happy coding!