What do I need to do after adding the ASP.NET Core Identity via a scaffold to a Blazor server app?

David Thielen 2,111 Reputation points
2023-03-28T18:43:45.4766667+00:00

I have a ASP.NET Core Blazor server app that I am adding ASP.NET Core Identity to. I am setting the identity DB to be a distinct DB from the app DB. Here's what I found needed to be done after the scaffolding completes:

It does insert the following in Program.cs:

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
	.AddEntityFrameworkStores<UserDbContext>();

But it did not add the following and so you need to (I think the scaffold assumes you will share the app DbContext). Note that there is no longer an IdentityHostingStartup.cs file.

builder.Services.AddDbContextFactory<UserDbContext>(options =>
	options.UseSqlServer(builder.Configuration.GetConnectionString("UserDbContextConnection")));

To create the database, as you now have two DbContext classes, you need to specify the context.

add-migration createUser -context UserDbContext
update-database -context UserDbContext

In Program.cs add:

app.UseAuthentication();
app.UseAuthorization();

XSRF Token

To protect against cross-site request forgery attacks on a logout, you need an XSRF token. In Blazor there is no direct access to the HttpContext, so this has to be grabbed and copied. This requires the following.

Create the file Services/TokenProvider.cs

namespace LouisHowe.Services
{
	public class TokenProvider
	{
		public string XsrfToken { get; set; }
		public string Cookie { get; set; }

	}

	public class InitialApplicationState
	{
		public string XsrfToken { get; set; }
		public string Cookie { get; set; }

	}
}

At the top of App.razor, insert:

@inject Services.TokenProvider TokenProvider

@code {
	[Parameter]
	public Services.InitialApplicationState InitialState { get; set; }

	protected override Task OnInitializedAsync()
	{
		TokenProvider.XsrfToken = InitialState.XsrfToken;
		TokenProvider.Cookie = InitialState.Cookie;

		return base.OnInitializedAsync();
	}
}

In Program.cs add:

builder.Services.AddScoped<TokenProvider>();

In _Host.cshtml we now populate the token provider:

<!-- top of page -->
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf

<!-- right after <body> -->
    @{
        var initialTokenState = new Services.InitialApplicationState
        {
            XsrfToken = Xsrf.GetAndStoreTokens(HttpContext).RequestToken,
            Cookie = HttpContext.Request.Cookies[".AspNetCore.Cookies"]
        };
    }

<!-- we are adding param-InitialState to the existing <component> -->
    <component type="typeof(App)" render-mode="ServerPrerendered" param-InitialState="initialTokenState"/>

First add the following to _Imports.razor. You may need to pull in via NuGet (if you don't have it yet) Microsoft.AspNetCore.Components.Authorization.

@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization

In NavMenu.razor you need to add the <AuthorizeView>, <Authorized>/<NotAuthorized> and add a login & logout menu item. The logout needs to be a POST to avoid cross scripting attacks and therefore it's actually a form with a submit button (more for that further down).

The HTML for the menu itself can be anything you want as this is just an example of the framework it needs to be in and the logout form post.

Also note that the urls to login & logout do not point to a file in your solution (unless you told the scaffold to instantiate them). They are in the Identity DLL. But they have a resource path in that DLL that matches where they would be if instantiated with the same namespace/filename.

<!-- top of page -->
@inject Services.TokenProvider TokenProvider

<!-- in the menu items section -->
 <div class="menu list-group list-group-flush">
        <AuthorizeView>
            <Authorized>
                <NavLink class="list-group-item list-group-item-action bg-light"
                         href="/" Match="NavLinkMatch.All">
                    <span class="oi oi-home" aria-hidden="true"></span> Home
                </NavLink>
                <NavLink class="list-group-item list-group-item-action bg-light"
                         href="/employeeoverview">
                    <span class="oi oi-list-rich" aria-hidden="true"></span> Employees
                </NavLink>
                <NavLink class="list-group-item list-group-item-action bg-light"
                         href="/employeeedit">
                    <span class="oi oi-list-rich" aria-hidden="true"></span> Add new employee
                </NavLink>
href="Logout">
            <span class="oi oi-list-rich" aria-hidden="true"></span> Log out
        </NavLink>*@
                <form action="/identity/account/logout" method="post">
                    <button class="nav-link btn btn-link" type="submit">
                        <span class="oi oi-list-rich" aria-hidden="true"></span> Log out (@context.User.Identity.Name)
                    </button>
                    <input name="__RequestVerificationToken" type="hidden"
                           value="@TokenProvider.XsrfToken">
                </form>
            </Authorized>
            <NotAuthorized>
                <NavLink class="list-group-item list-group-item-action bg-light" href="/identity/account/Login">
                    <span class="oi oi-list-rich" aria-hidden="true"></span> Log in
                </NavLink>
            </NotAuthorized>
        </AuthorizeView>
    </div>

To get the conditional elements to work in the nav menu, add <CascadingAuthenticationState> to the HTML in App.razor. And put RouteData in an <AuthorizeRouteView> element.

<CascadingAuthenticationState>
	<Router AppAssembly="@typeof(App).Assembly">
		<Found Context="routeData">
			<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
			<FocusOnNavigate RouteData="@routeData" Selector="h1" />
		</Found>
		<NotFound>
			<PageTitle>Not found</PageTitle>
			<LayoutView Layout="@typeof(MainLayout)">
				<p role="alert">Sorry, there's nothing at this address.</p>
			</LayoutView>
		</NotFound>
	</Router>
</CascadingAuthenticationState>

And you're done!

References:

ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,075 questions
Blazor
Blazor
A free and open-source web framework that enables developers to create web apps using C# and HTML being developed by Microsoft.
1,349 questions
{count} votes