My latest (and oldest) project is NoSearch - it’s a curated search engine - that is, it allows you to submit a site (a little like Hacker News) except that it’s intended for sites that are perennially useful. It allows you to create lists of these; for example, here’s my personal list of funny / fascinating sites.
Anyway, enough of the sly plugs, it’s nowhere near ready yet, but the latest thing that I needed to do was to integrate Google Sign-in (as I have no interest in keeping passwords). The interesting thing that I found with Asp.Net Core is that it seems to push you heavily down this route - and worse, most of the functionality is hidden away. You can achieve what I’m describing here by simply creating a new MVP project with “individual accounts” and just add AddGoogle to the AddIdentity call. I’ve tried it - it works flawlessly; however, you can only see about 5% of the code in your project, and it rigs EF up to a DB from your app.
Consequently, from the tutorials that I saw, the path of least resistance is to do just that.
In this article, I cover integrating an external provider without wiring it up to EFor any database. Obviously, at some stage, you are going to need to do this.
Add Authentication
The first step is to add authentication to your project; if you don’t already have this, it sits in the startup.cs / program.cs:
builder.Services.AddAuthentication("cookie")
.AddCookie("cookie")
.AddGoogle(options =>
{
options.ClientId = "google-id";
options.ClientSecret = "google-secret";
options.SignInScheme = "cookie";
});
This sets up the sign-in and wires it up with the auth cookie.
I won’t cover the retrieval of secrets from KeyVault or something similar - it’s pretty tangential to what we’re discussing.
The only other thing that you need to do is to add the authentication middleware:
app.UseAuthentication();
app.UseAuthorization();
The next step is to add the call to the Google sign-in. I created a SignInController:
[HttpGet]
public IActionResult SignInWithGoogle()
{
var redirectUrl = Url.Action(nameof(GoogleCallback), "SignIn", null, Request.Scheme);
var properties = new AuthenticationProperties
{
RedirectUri = redirectUrl,
Items =
{
{ "scheme", "Google" },
}
};
return Challenge(properties, "Google");
}
[HttpGet]
public async Task<IActionResult> GoogleCallback()
{
var result = await HttpContext!.AuthenticateAsync(GoogleDefaults.AuthenticationScheme);
if (result.Succeeded && result.Principal != null)
{
var googleUser = result.Principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var claimsPrincipal = new ClaimsPrincipal(result.Principal);
var claims = claimsPrincipal.Claims.ToList();
var principal = new ClaimsPrincipal(
new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme));
await HttpContext.SignInAsync("cookie",
principal,
new AuthenticationProperties()
{
IsPersistent = true
});
}
return RedirectToAction("Index", "Home");
}
This basically calls out to Google, and registers the callback address. You can add something into your view that allows you to see that the user is logged in (this is an excerpt from my LoginPartial):
<ul class="navbar-nav">
@if (User?.Identity?.IsAuthenticated ?? false)
{
<ul class="navbar-nav align-items-center">
<li class="nav-item">
<a class="nav-link text-dark" asp-controller="Account" asp-action="Index">Signed in as @User.Identity.Name</a>
</li>
That’s basically all you need. It took me a comically long amount of time to get this working, while constantly heading down dead-ends and re-writing over and over. As usual, this article is for me in 6 months when I start down the same dead-ends.