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

Sep 26

Written by: Michael Washington
9/26/2019 5:11 AM  RssIcon

 

To demonstrate how authentication works in a server-side Blazor application, we will strip authentication down to its most basic elements. We will simply set a cookie then read that cookie in the application.

 

Application Authentication

image

Most business web applications require their users to log into the application.

 

image

The user enters their username and password, that are checked against a membership database.

 

image

Once authenticated, the application recognizes the user and now has the ability to deliver content securely.

Once the authentication process of a server-side Blazor application is understood, we can then implement an authentication and membership management system that meets our needs (for example, one that allows users to create and manage their user accounts).

NOTE: This sample code does not check to see if a person is using a legitimate username and password! You would need to add the proper code to check. This code is just a demonstration of how the process of authorizing a user works.

For a complete application see: Creating A Step-By-Step End-To-End Database Server-Side Blazor Application.

 

Create The Application

image

Open Visual Studio 2019.

 

image

Create a Blazor Server App without authentication.

 

Add Nuget Packages

image

In the Solution Explorer, right-click on the client project and select Manage NuGet Packages.

 

image

Add references to the following libraries:

  • Microsoft.AspNetCore.Authorization
  • Microsoft.AspNetCore.Http
  • Microsoft.AspNetCore.Blazor.HttpClient
  • Microsoft.AspNetCore.Identity

 

Add Cookie Authentication

image

Open the Startup.cs file.

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

 

// ******
// BLAZOR COOKIE Auth Code (begin)
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using System.Net.Http;
// BLAZOR COOKIE Auth Code (end)
// ******
 

Alter the Startup class to the following, adding the sections marked BLAZOR COOKIE Auth Code:

 

    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        public IConfiguration Configuration { get; }
        public void ConfigureServices(IServiceCollection services)
        {
            // ******
            // BLAZOR COOKIE Auth Code (begin)
            services.Configure<CookiePolicyOptions>(options =>
            {
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });
            services.AddAuthentication(
                CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie();
            // BLAZOR COOKIE Auth Code (end)
            // ******
            services.AddRazorPages();
            services.AddServerSideBlazor();
            services.AddSingleton<WeatherForecastService>();
            // ******
            // BLAZOR COOKIE Auth Code (begin)
            // From: https://github.com/aspnet/Blazor/issues/1554
            // HttpContextAccessor
            services.AddHttpContextAccessor();
            services.AddScoped<HttpContextAccessor>();
            services.AddHttpClient();
            services.AddScoped<HttpClient>();
            // BLAZOR COOKIE Auth Code (end)
            // ******
        }
        // This method gets called by the runtime. 
        // Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseRouting();
            // ******
            // BLAZOR COOKIE Auth Code (begin)
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();
            app.UseAuthentication();
            // BLAZOR COOKIE Auth Code (end)
            // ******
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
        }
    }

 

First the code adds support for cookies. Cookies are created by the application, and passed to the user’s web browser when the user logs in. The web browser passes the cookie back to the application to indicate that the user is authenticated. When the user ‘logs out’, the cookie is removed.

This code also adds:

  • HttpContextAccessor
  • HttpClient

as services that will be accessed in the code using dependency Injection.

See this link for a full explanation of how HttpContextAccessor allows us to determine who the logged in user is.

 

Add Login/Logout Pages

image

Logging in (and out) is performed by .cshtml pages.

Add the following Razor pages and code:

 

Login.cshtml

 

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

 

Login.cshtml.cs

 

using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace BlazorCookieAuth.Server.Pages
{
    [AllowAnonymous]
    public class LoginModel : PageModel
    {
        public string ReturnUrl { get; set; }
        public async Task<IActionResult> 
            OnGetAsync(string paramUsername, string paramPassword)
        {
            string returnUrl = Url.Content("~/");
            try
            {
                // Clear the existing external cookie
                await HttpContext
                    .SignOutAsync(
                    CookieAuthenticationDefaults.AuthenticationScheme);
            }
            catch { }
            // *** !!! This is where you would validate the user !!! ***
            // In this example we just log the user in
            // (Always log the user in for this demo)
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.Name, paramUsername),
                new Claim(ClaimTypes.Role, "Administrator"),
            };
            var claimsIdentity = new ClaimsIdentity(
                claims, CookieAuthenticationDefaults.AuthenticationScheme);
            var authProperties = new AuthenticationProperties
            {
                IsPersistent = true,
                RedirectUri = this.Request.Host.Value
            };
            try
            {
                await HttpContext.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme,
                new ClaimsPrincipal(claimsIdentity),
                authProperties);
            }
            catch (Exception ex)
            {
                string error = ex.Message;
            }
            return LocalRedirect(returnUrl);
        }
    }
}

 

Logout.cshtml

 

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

 

Logout.cshtml.cs

 

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace BlazorCookieAuth.Server.Pages
{
    public class LogoutModel : PageModel
    {
        public async Task<IActionResult> OnGetAsync()
        {
            // Clear the existing external cookie
            await HttpContext
                .SignOutAsync(
                CookieAuthenticationDefaults.AuthenticationScheme);
            return LocalRedirect(Url.Content("~/"));
        }
    }
}

 

Add Client Code

image

Add a page called LoginControl.razor to the Shared folder using the following code:

 

@page "/loginControl"
<AuthorizeView>
    <Authorized>
        <b>Hello, @context.User.Identity.Name!</b>
        <a class="ml-md-auto btn btn-primary"
           href="/logout?returnUrl=/"
           target="_top">Logout</a>
    </Authorized>
    <NotAuthorized>
        <input type="text"
               placeholder="User Name"
               @bind="@Username" />
        &nbsp;&nbsp;
        <input type="password"
               placeholder="Password"
               @bind="@Password" />
        <a class="ml-md-auto btn btn-primary"
           href="/login?paramUsername=@Username&paramPassword=@Password"
           target="_top">Login</a>
    </NotAuthorized>
</AuthorizeView>
@code {
    string Username = "";
    string Password = "";
}

 

This code creates a login component that uses the AuthorizeView component to wrap markup code based on the user’s current authentication.

If the user is logged in, we display their name and a Logout button (that navigates the user to the logout page created earlier).

If they are not logged in, we display username and password boxes and a Login button (that navigates the user to the login page created earlier).

 

image

Finally, we alter the MainLayout.razor page (in the Shared folder) to the following:

 

@inherits LayoutComponentBase
<div class="sidebar">
    <NavMenu />
</div>
<div class="main">
    <div class="top-row px-4">
        <!-- BLAZOR COOKIE Auth Code (begin) -->
        <LoginControl />
        <!-- BLAZOR COOKIE Auth Code (end) -->
    </div>
    <div class="content px-4">
        @Body
    </div>
</div>

 

This adds the login component to the top of every page in the Blazor application.

 

image

Open the App.razor page and surround all the existing code in a CascadingAuthenticationState tag.

 

image

We can now hit F5 to run the application.

 

image

We can enter a username and password and click the Login button…

 

image

We can then look in the Google Chrome Web Browser DevTools and see the cookie has been created.

 

image

When we click Logout

 

image

The cookie is removed.

 

Calling Server Side Controller Methods

At this point all the .razor pages will properly detect if the user is authenticated, and operate as expected. However, if we make a http request to a server side controller, the authenticated user will not be properly detected.

 

image

To demonstrate this, we first open the startup.cs page and add the following code to the end of the app.UseEndpoints method, (under the endpoints.MapFallbackToPage("/_Host"); line), to allow http requests to controllers to be properly routed:

 

        // ******
        // BLAZOR COOKIE Auth Code (begin)
        endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
        // BLAZOR COOKIE Auth Code (end)
        // ******

 

 

image

Next, we create a Controllers folder and add a UserController.cs file with the following code:

 

using Microsoft.AspNetCore.Mvc;
namespace BlazorCookieAuth.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class UserController : Controller
    {
        // /api/User/GetUser
        [HttpGet("[action]")]
        public UserModel GetUser()
        {
            // Instantiate a UserModel
            var userModel = new UserModel
            {
                UserName = "[]",
                IsAuthenticated = false
            };
            // Detect if the user is authenticated
            if (User.Identity.IsAuthenticated)
            {
                // Set the username of the authenticated user
                userModel.UserName = 
                    User.Identity.Name;
                userModel.IsAuthenticated = 
                    User.Identity.IsAuthenticated;
            };
            return userModel;
        }
    }
    // Class to hold the UserModel
    public class UserModel
    {
        public string UserName { get; set; }
        public bool IsAuthenticated { get; set; }
    }
}

 

image

We add a new .razor page, CallServerSide.razor, using the following code:

 

@page "/CallServerSide"
@using BlazorCookieAuth.Controllers
@using System.Net.Http
@inject HttpClient Http
@inject NavigationManager UriHelper
@inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
<h3>Call Server Side</h3>
<p>Current User: @CurrentUser.UserName</p>
<p>IsAuthenticated: @CurrentUser.IsAuthenticated</p>
<button class="btn btn-primary" @onclick="GetUser">Get User</button>
@code {
    UserModel CurrentUser = new UserModel();
    async Task GetUser()
    {
        // Call the server side controller
        var url = UriHelper.ToAbsoluteUri("/api/User/GetUser");
        var result = await Http.GetJsonAsync<UserModel>(url.ToString());
        // Update the result
        CurrentUser.UserName = result.UserName;
        CurrentUser.IsAuthenticated = result.IsAuthenticated;
    }
}

 

Finally, we use the following code to add a link to the page in Shared/NavMenu.razor:

 

        <li class="nav-item px-3">
            <NavLink class="nav-link" href="CallServerSide">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Call Server Side
            </NavLink>
        </li>

 

 

 

image

We run the application and log in.

 

image

We navigate to the new Call Server Side control and click the Get User button (that calls the UserController.cs that we just added), and it does not detect the logged in user.

To resolve this, change the GetUser method in the CallServerSide.razor page to the following:

 

    async Task GetUser()
    {
        // Code courtesy from Oqtane.org (@sbwalker)
        // We must pass the authentication cookie in server side requests
        var authToken =
        HttpContextAccessor.HttpContext.Request.Cookies[".AspNetCore.Cookies"];
        if (authToken != null)
        {
            Http.DefaultRequestHeaders
            .Add("Cookie", ".AspNetCore.Cookies=" + authToken);
            // Call the server side controller
            var url = UriHelper.ToAbsoluteUri("/api/User/GetUser");
            var result = await Http.GetJsonAsync<UserModel>(url.ToString());
            // Update the result
            CurrentUser.UserName = result.UserName;
            CurrentUser.IsAuthenticated = result.IsAuthenticated;
        }
    }

 

We have an authentication cookie, we just need to pass it in the DefaultRequestHeaders.

 

image

Now, when we log in, and click the Get User button, the controller method is able to detect the logged in user.

 

Special Thanks

This article would not be possible without sample code (using the full .Net Core Membership provider) provided by SQL-MisterMagoo.

Additional research provided by Shaun Walker.

 

Links

Blazor.net

Use cookie authentication without ASP.NET Core Identity

ASP.NET Core Blazor authentication and authorization

BlazorTest (SQL-MisterMagoo's site)

Authentication for serverside Blazor (How to use IHttpContextAccessor)

Blazor Security/Authorization

 

Download

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

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

Tags: Blazor
Categories:

27 comment(s) so far...


Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

I've tried this and I get it to work. But if I remove the cookie I can still click on the menu items and use the site until I reload the page.
I've been trying to find out what I need to add but I haven't been able to find anything.
Do you know if I need to add anything for Blazor to log me out without reloading the page?

By Fredrik Nilsson on   8/6/2019 6:14 AM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

@Fredrik Nilsson - A new HTTP request (a page refresh), is required in order to become a different user (in this case to log the user out).

By Michael Washington on   8/6/2019 6:18 AM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

Hi Michael:

Is there a way to customize the default existing the login and register pages in a Blazor Server Side application or I need to create a new component for the login page?

Thanks

David

By David Presa on   8/12/2019 3:49 PM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

@David Pres - It may be possible to customize the existing ones, but, I have not tried it myself.

By Michael Washington on   8/12/2019 3:50 PM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

Having the toughest time getting this piece to work. I have it in on a Login.Razor page in the code block.

HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);

I'm using server-side Blazor with .Net Core 3.0 & I cannot figure out what dependency injection to use. If I don't do any injection for HttpContext, I get "The name 'HttpContext' does not exist in the current context."

By Katie P on   8/22/2019 8:56 AM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

@Katie P - Please download the sample from the download page on this site and compare it to your own. You must perform a full page refresh to log a user in, therefore I do not believe logging on on a .razor page will work..

By Michael Washington on   8/22/2019 9:02 AM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

Hi Michael:

I tried to register to your web site to download the source code for testing but there is a Captcha error in the registration form. Please could you check that, because I need to test it. Itried with Google Chrome and with Edge and same issue.

Thanks

David

By David on   8/25/2019 12:09 PM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

@David - Fixed! Thanks for letting me know!

By Michael Washington on   8/25/2019 12:09 PM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

Hi Michael:

I am trying to adapt my code to your cookie implementation. The part that we need is to create a cookie, because we have a JWT autehntication in place from our web api.

Is there a way to read the HttpContextAccessor that was populated with the claims inside your LoginModel class. The idea would be to create the cookie only, because we already authenticated from the web api, I tried to inject the dependency of HttpContextAccessor inside the LoginModel but all is null.

As you know I cant create the cookie inside a Blazor component, so we want to use your code only to create the cookie. Any idea how to retrieve the claims from the HttpContextAccessor instance.

Appreciate any help on this.

Thanks

David

By David on   8/25/2019 6:17 PM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

@David - While you cannot create the cookie without doing a postback (as I am doing) there should not be anything preventing you from reading the cookie. This is what this line does: var authToken =
HttpContextAccessor.HttpContext.Request.Cookies[".AspNetCore.Cookies"]; If you need more help post your code to StackOverflow.com and let me know on Twitter: @ADefWebserver.

By Michael Washington on   8/25/2019 6:21 PM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

Does anyone have any tip on using post to send the username and password to the loginpage? As it is now the username and password is written in clear text in the address bar when I publish it to the iis.
Thanks in advance!

By Fredrik Nilsson on   8/26/2019 10:02 AM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

@Fredrik Nilsson - You could alter the code to do a http 'form post' to the page rather than a http 'get' as it currently is. See this link for help: https://www.learnrazorpages.com/razor-pages/handler-methods

By Michael Washington on   8/26/2019 10:06 AM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

Hi Michael:

Could you explain me why for a Blazor Server Side application you need to add these services twice:

services.AddHttpContextAccessor();
services.AddScoped();

services.AddHttpClient();
services.AddScoped();

I read the link that you provided but I still not clear. I thought that only the scoped versions would be enough.

Thanks in advanced

By David on   8/27/2019 2:30 PM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

@David - I think the link https://github.com/aspnet/Blazor/issues/1554 covers the "why" we need this. As to a deeper discussion of how we add a service then add it as a scoped service, StackOverflow would be the place because the comments section of this blog post is not really good for this. Thanks!

By Michael Washington on   8/27/2019 2:36 PM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

Hello,

Im having problems with this piece of code

await ContextAccessor.HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);

It throws an exception: The response headers cannot be modified because the response has already started.

Do you know why this happens?Thank you

By Miguel on   10/3/2019 4:28 AM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

@Miguel - Did you get that error from the code you downloaded from this site?

By Michael Washington on   10/3/2019 4:29 AM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

Hi, i have notice that you call login razor page passing in query string username and password. Is not dangerour to pass them in this way? Is not possibile to pass in post request? I have tried to pass in post but the redirect not work.
Thanks

By Roberto on   10/7/2019 1:20 PM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

@Roberto - I couldn't get a post to work either so the safest thing to do would be to not have them enter their username and password on the .razor page but instead direct them to the .cshtml page and have them enter their username and password there.

By Michael Washington on   10/7/2019 1:22 PM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

So i think is bettere to have a razor cshtml page where in code behind there is the login logic, and maybe render the razor component (login form) directly on it. Right? So in this way we can solve the problem of credential in query string

By Roberto on   10/8/2019 4:29 AM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

@Roberto - You simply send the user to the .cshtml page to log in, then re-direct them back to the .razor pages. No need to 'embed the .razor page in the .cshtml page. This is what the default login page in Blazor does.

By Michael Washington on   10/8/2019 4:31 AM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

I don't understand so weel, do you have an example?

By Roberto on   10/8/2019 4:00 PM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

@Roberto - If you follow the tutorial here: http://blazorhelpwebsite.com/Blog/tabid/61/EntryId/4318/Creating-A-Step-By-Step-End-To-End-Database-Server-Side-Blazor-Application.aspx it will create the login page the way I described.

By Michael Washington on   10/8/2019 4:01 PM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

Sorry, but I don't find the login page in your "reating-A-Step-By-Step-End-To-End-Database-Server-Side-Blazor-Application" solution!

By Roberto on   10/9/2019 4:20 AM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

@Roberto - You can follow these directions to make the Login page show: https://docs.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-3.0&tabs=visual-studio#scaffold-identity-into-a-razor-project-with-authorization

By Michael Washington on   10/9/2019 4:35 AM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

I've been trying to find this online but no success.
I have sliding expiration but the cookie (that is set to 5 minutes in this case) always expires after 5 minutes.
What might I have missed?

startup.cs
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
options.LoginPath = "/";
options.AccessDeniedPath = "/";
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(6);
});

By Fredrik Nilsson on   11/4/2019 5:37 AM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

@Fredrik Nilsson - Sorry I never tried that :( It is probably a combination of parameters. There are probably additional "options." that you need to set.

By Michael Washington on   11/4/2019 5:40 AM
Gravatar

Re: A Demonstration of Simple Server-side Blazor Cookie Authentication

Many thanks, I'm so gratefull for your work, works perfect for me

By Irakli on   11/13/2019 5:45 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