When you buy this book you support this site! - Thank You for your support!

Oct 3

Written by: Michael Washington
10/3/2019 5:55 PM  RssIcon

You can log users into your server side Blazor application using Google authentication.

Server-side Blazor provides options for deeper integration between the ‘client side’ and ‘server side’ code because the ‘client side’ code is processed server-side. With server-side Blazor, we end up using less code, and things are a lot less complex because we can trust that the end-user was not able to alter or hack the ‘client side’ code.

If the ‘client side’ code says a user is who they say they are, we can trust them. Otherwise, we have to always make a ‘round trip’ to the server, before permitting operations or returning sensitive data. The ‘round trip’ is still being made (because the ‘client site’ code is being generated ‘server side’), but, our code structure is now cleaner and more streamlined.

Note: This example works without using the Google+ API that Google started to shut down. This sample incorporates the work-around that Microsoft is in the process of rolling out officially. See this GitHub issue for more information.

Note: Many examples, showing Google authentication in an .Net Core application, use the SignInManager. The problem is, the SignInManager requires a data store. This example does not.

 

The Application

 image

Initially the user clicks the Login button.

 image

They are taken to the Google Login page where they enter their Google username and password.

Note: If the user is already logged into Google, they will be automatically logged in without the need to go through this step.

 image

They are returned to the application.

Their name, and if they have an Avatar, will display.

The Logout button will also appear. Clicking the Logout button will log the user out of the application.

 

Create The Application

image

Open Visual Studio 2019 (or higher).

 image

Select Create a new project.

image

Select Blazor App.

image

Name the project BlazorGmail.

 

Add Nuget Package

 image

Right-click on the solution node in the Solution Explorer, and select Manage NuGet Packages for Solution.

 image

Install:

Microsoft.AspNetCore.Authentication.Google

 

Set Up the Google Application

 image

You need to create a Google API Console project to obtain a client ID.

Go to:

https://developers.google.com/identity/sign-in/web/devconsole-project

Sign in with your Google account and click CONFIGURE A PROJECT.

 image

Give the project a name.

 image

Fill in the information for the remaining screens

 image

Select Other for OAuth client.

Click the CREATE button.

 image

Copy and save the Client ID and Client Secret and click the DONE button.

Note: You can return to the configuration at anytime by going to:

https://console.developers.google.com

 

Configure the Authentication Pipeline

 image

Open the appsettings.json file and change all the code to the following:

 
{
  "Google": {
    "Instance": "https://accounts.google.com/o/oauth2/v2/auth",
    "ClientId": "{{{ YOUR APP ID }}}",
    "ClientSecret": "{{{ YOUR CLIENT SECRET }}}",
    "CallbackPath": "/signin-google"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}
 
Replace  {{{ YOUR APP ID }}} with the Client ID you saved earlier. 

Replace  {{{ YOUR CLIENT SECRET }}} with the Client Secret you saved earlier.

 image

Open the Startup.cs file.

Add the following using statements to the top of the file:

 
using System.Net.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
 

Add the following to the Startup class:

 
        // To hold the values from the appsettings.json file
        public IConfiguration Configuration { get; }
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
 

Add the following to the bottom of public void ConfigureServices(IServiceCollection services) method:

 

            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie();
            services.AddAuthentication().AddGoogle(options =>
            {
                options.ClientId = Configuration["Google:ClientId"];
                options.ClientSecret = Configuration["Google:ClientSecret"];
                options.ClaimActions.MapJsonKey("urn:google:profile", "link");
                options.ClaimActions.MapJsonKey("urn:google:image", "picture");
            });
            // From: https://github.com/aspnet/Blazor/issues/1554
            // Adds HttpContextAccessor
            // Used to determine if a user is logged in
            // and what their username is
            services.AddHttpContextAccessor();
            services.AddScoped<HttpContextAccessor>();
            // Required for HttpClient support in the Blazor Client project
            services.AddHttpClient();
            services.AddScoped<HttpClient>();
            // Pass settings to other components
            services.AddSingleton<IConfiguration>(Configuration);

 

Add the following to the public void Configure(IApplicationBuilder app, IHostingEnvironment env) method (after the app.UseStaticFiles() line):

 
            app.UseCookiePolicy();
            app.UseAuthentication();
 

 image

Save the file.

Rebuild the Solution.

It should build without errors.

 

Create Login and Logout Pages

image

In the Pages folder, add the following pages using the following code:

 

Login.cshtml

 

@page
@model BlazorGmail.Server.Pages.LoginModel
@{
    ViewData["Title"] = "Log in";
}
<h2>Login</h2>

 

Login.cshtml.cs

 

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace BlazorGmail.Server.Pages
{
    [AllowAnonymous]
    public class LoginModel : PageModel
    {
        public IActionResult OnGetAsync(string returnUrl = null)
        {
            string provider = "Google";
            // Request a redirect to the external login provider.
            var authenticationProperties = new AuthenticationProperties
            {
                RedirectUri = Url.Page("./Login", 
                pageHandler: "Callback", 
                values: new { returnUrl }),
            };
            return new ChallengeResult(provider, authenticationProperties);
        }
        public async Task<IActionResult> OnGetCallbackAsync(
            string returnUrl = null, string remoteError = null)
        {
            // Get the information about the user from the external login provider
            var GoogleUser = this.User.Identities.FirstOrDefault();
            if (GoogleUser.IsAuthenticated)
            {
                var authProperties = new AuthenticationProperties
                {
                    IsPersistent = true,
                    RedirectUri = this.Request.Host.Value
                };
                await HttpContext.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme,
                new ClaimsPrincipal(GoogleUser),
                authProperties);
            }
            return LocalRedirect("/");
        }
    }
}

 

Logout.cshtml

 

@page
@model BlazorGmail.Server.Pages.LogoutModel
@{
    ViewData["Title"] = "Logout";
}
<h2>Logout</h2>
 

Logout.cshtml.cs

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace BlazorGmail.Server.Pages
{
    public class LogoutModel : PageModel
    {
        public string ReturnUrl { get; private set; }
        public async Task<IActionResult> OnGetAsync(
            string returnUrl = null)
        {
            returnUrl = returnUrl ?? Url.Content("~/");
            // Clear the existing external cookie
            try
            {
                await HttpContext
                    .SignOutAsync(
                    CookieAuthenticationDefaults.AuthenticationScheme);
            }
            catch (Exception ex)
            {
                string error = ex.Message;
            }
            return LocalRedirect("/");
        }
    }
}

 

Create Login Control

image

In the Shared folder, add LoginControl.razor using the following code:

 

@using System.Security.Claims
@using Microsoft.AspNetCore.Http
@inject IHttpContextAccessor _httpContextAccessor
@inject HttpClient Http
@if (User.Identity.Name != null)
{
    <img src="@Avatar" />
    <b>You are logged in as: @GivenName @Surname</b>
    <a class="ml-md-auto btn btn-primary"
       href="/Logout"
       target="_top">Logout</a>
}
else
{
    <a class="ml-md-auto btn btn-primary"
       href="/Login"
       target="_top">Login</a>
}
@code {
    private ClaimsPrincipal User;
    private string GivenName;
    private string Surname;
    private string Avatar;
    protected override void OnInitialized()
    {
        base.OnInitialized();
        try
        {
            // Set the user to determine if they are logged in
            User = _httpContextAccessor.HttpContext.User;
            // Try to get the GivenName
            var givenName =
                _httpContextAccessor.HttpContext.User
                .FindFirst(ClaimTypes.GivenName);
            if (givenName != null)
            {
                GivenName = givenName.Value;
            }
            else
            {
                GivenName = User.Identity.Name;
            }
            // Try to get the Surname
            var surname =
                _httpContextAccessor.HttpContext.User
                .FindFirst(ClaimTypes.Surname);
            if (surname != null)
            {
                Surname = surname.Value;
            }
            else
            {
                Surname = "";
            }
            // Try to get Avatar
            var avatar =
            _httpContextAccessor.HttpContext.User
            .FindFirst("urn:google:image");
            if (avatar != null)
            {
                Avatar = avatar.Value;
            }
            else
            {
                Avatar = "";
            }
        }
        catch { }
    }
}

 

 

image

To display the Login control, open Shared/MainLayout.razor and change all the code to the following:

 

@inherits LayoutComponentBase
<div class="sidebar">
    <NavMenu />
</div>
<div class="main">
    <div class="top-row px-4">
        <!-- ******************************** -->
        <LoginControl />
        <!-- ******************************** -->
    </div>
    <div class="content px-4">
        @Body
    </div>
</div>

 

Links

Blazor.net

Google external login setup in ASP.NET Core

Google Developers Dashboard

 

Download

The project is available at http://BlazorHelpWebsite.com/Downloads.aspx

You must have Visual Studio 2019 (or higher) installed to run the code.

Tags: Blazor
Categories:

11 comment(s) so far...


Gravatar

Re: Google Authentication in Server Side Blazor

Hi, Michael, thank you for your step by step tutorial to integrate Google authentication in server-side Blazor. I reproduce all of your steps, but when I click at "Login", the app is trying to get google auth screen, but end in default MainLayout message "Sorry, there's nothing at this address".

By Alexander on   11/7/2019 4:09 AM
Gravatar

Re: Google Authentication in Server Side Blazor

@Alexander - Please download the code from the download page on this site and compare it to your own.

By Michael Washington on   11/7/2019 4:09 AM
Gravatar

Re: Google Authentication in Server Side Blazor

Hi, again Michael, I wrote a comment before a few hours, that I can`t log in with google. Everything now is ok. I am not sure what was the problem, maybe the order in ConfigureService, or this line in .csproj - . I am not sure but now is working and I am going to resize the avatar because it is too big and somehow uses google account and relates to my own app user and store in EntityFramework to have more data. Thank you for your post :)!

By Sashko on   11/7/2019 4:37 AM
Gravatar

Re: Google Authentication in Server Side Blazor

Hi, Michael, as I mentioned last week, all of your code is working properly. I am trying for many hours to add IdentityCore, and use the extended user that I have been created and the "Roles". The problem is that every time when I am getting the callback from google the "ClaimsPrincipal PageModel.User" is empty. When I comment out "services.AddIdentityCore()", everything is fine, but I cannot use identity functions. I don`t want to use the build-in Identity template, because I only want Google login, after that, I will get the user from my db and used additional properties that I have. I was close to success, but I get "InvalidOperationException: No sign-in authentication handler is registered for the scheme 'IdentityApplication'. The registered sign-in schemes are: Cookies". Sorry for the long comment, but I already spent many hours and decide to write you. Thank you for your time :).

By Sashko on   11/12/2019 5:06 AM
Gravatar

Re: Google Authentication in Server Side Blazor

@Sashko - I am also unable to use any of the built-in Microsoft identity assemblies or code with this Google log in code. You can see how I handle everything 'manually' in the following project: https://github.com/ADefWebserver/FatSecretDataExporter/blob/master/FatSecretWebApp/Pages/GetData.razor

By Michael Washington on   11/12/2019 5:08 AM
Gravatar

Re: Google Authentication in Server Side Blazor

Finally, it is ready! Michael, I succeed, and now I have:
1. Identity.
2. External login from Google.

The difference is this, that I scaffolded Identity, make some changes on Register, Login, External Login, make some other edits, and now the system leans only on google accounts, even I can put a role when a user is login for the first time in the system. If you want I can write the steps, or make a Github repo with a sample project. Thank you for your time and tutorials.

P.S. Check your site security code function because every time, my first attempt is failed, and after that in second try, it is okay.

By Sashko on   11/12/2019 12:05 PM
Gravatar

Re: Google Authentication in Server Side Blazor

@Sashko - Great news. A GitHub repo would be really helpful for people

By Michael Washington on   11/12/2019 12:06 PM
Gravatar

Re: Google Authentication in Server Side Blazor

Thank you for this great explanation. I'm restricting my users to be from my domain (Google G Suite) and I added a custom role (MyAppAdmin) to a user in Google G Suite. How can I get the role in Blazor? In `OnCreatingTicket` `context.User` doesn't contain this role.

Second question. Because we're limiting access to our domain users we can block or delete users. The app is now never checking for this. How to configure that the user/cookie is validated now and then? I posted this also on https://stackoverflow.com/questions/59194273/how-to-validate-if-a-user-logged-in-with-google-is-still-valid

Again, this is a great site. I will also purchase your book in a few days.

By Paul Meems on   12/5/2019 6:17 AM
Gravatar

Re: Google Authentication in Server Side Blazor

@Paul Meems - You have to enable the ability to call Google APIs (https://www.googleapis.com/auth/admin.directory.user, https://www.googleapis.com/auth/admin.directory.group) to get this information, but, before you can do that, the G-Suite Domain Admin has to authorize that access using https://developers.google.com/admin-sdk/directory/v1/guides/authorizing

By Michael Washington on   12/5/2019 6:28 AM
Gravatar

Re: Google Authentication in Server Side Blazor

Thanks again for your help. Just to let you know I've set up a new repo at https://bitbucket.org/pmeems/matblazorgooglegsuite/src/develop/ merging your examples and other samples I've found to create a Material Design Blazor application using Google Login and restrict to a specific domain. So far it works great, except for the extras like checking if the user is still valid and getting the roles from Google.
When you've got the time could you show me how to get the roles and validate the user?

By Paul Meems on   12/9/2019 9:05 AM
Gravatar

Re: Google Authentication in Server Side Blazor

@Paul Meems - I will respond to your post on Stack Overflow at: https://stackoverflow.com/questions/59194273/how-to-validate-if-a-user-logged-in-with-google-is-still-valid/59197327?noredirect=1#comment104701558_59197327

By Michael Washington on   12/9/2019 9:07 AM

Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
CAPTCHA image
Enter the code shown above in the box below
Add Comment   Cancel 
Microsoft Visual Studio is a registered trademark of Microsoft Corporation / LightSwitch is a registered trademark of Microsoft Corporation