skip to Main Content

What I thought would be a very simple task has turned out to be the opposite of that.

I’m building a fairly simple web app with blazor and I’ve got windows domain credential based authentication working fine with Negotiate. That was fairly simple to set up.

Now let’s say I want to display a "Welcome: " type of message on a banner or include the username to keep track of form submissions. Every solution I’ve come across seems to suggest doing something like this directly in the component I need to access the username in.

Normally, I would just store the username string that was entered in the username field, but since Negotiate uses that browser popup window for logins instead of a web page, I don’t think that’s possible. (maybe I’m wrong?)

This isn’t my exact code but it’s basically what I’m trying to do.
This works in MainLayout.razor but any other component inside of it will hit a null value exception.

@page "/"
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Authorization
@inject AuthenticationStateProvider AuthStateProvider
@attribute [Authorize]

<h1> Welcome Back: @username <h1>

@code{
    public string? username;
    protected override async OnInitializedAsync(){
        var authstate = await AuthStateProvider.GetAuthenticationStateAsync();
        username = authstate.User.Identity.Name.Substring(9);
    }
}

I think the issue may be the fact that the razor components are rendering twice which is causing the authstate variable to pull twice resulting in the antiforgery errors I keep getting.

I really think the key here is somehow forcing my components to only render once.
That way, I only need to pull the auth state at the time of login and cache only what I need from it so I can access it in all of my components without causing errors.
Performance is not a concern at all for me with this project. It’s a very lightweight application serving a very small number of users so I’m willing to sacrifice some performance to get this to work.

For all the time I’ve spent on this , I probably could have built my own traditional authentication system and login component and added a users table to my database.
But I wanted something that was more streamlined for users (eventually would like to move to an SSO model) and at this point, I feel like I’m in too deep.

I feel like this is probably a lot easier than I’m making it. I assumed negotiate would have some built in methods or properties that could be used to get the username without crashing my project but I guess not.

I’ve made a few games with Unity and C# and a few websites with flask and js, but this is my first real blazor project and I’ve been able to feel my way through most of it but this has completely brickwalled me.

Any help here is greatly appreciated.

Project Detials

  • Dotnet 8.0
  • Default blazor Template
  • InteractiveServer Render mode

I have tried several different solutions for this but none of them work.

The first thing I tried was the simplest which was simply calling GetAuthenticationStateAsync() in the constructor method of whatever razor component I needed the username in. Obviously this doesn’t work because of the double render problem.

The next thing I tried was putting around my router in routes.razor which irreparably screwed up my project and forced me to restore from backup. So I learned my lesson about even looking at anything with the word "route" in it. So Instead I tried encapsulating my MainLayout.razor in the block and while that didn’t destroy my project, it still doesn’t work. Same for when I try to simply pass only the username from MainLayout.razor with a . I found out the reason this doesn’t work, is again, because the razor components like to render twice. The reason I know this is because I put a Console.Writeline() statement in OnInitializedAsync() that is supposed to print the user name to the debug console. When I load the page, I get not one but TWO print statements and only the first contains the username. Meaning that the Cascading Parameter thing only passes the value on the first render.

Now I would LOVE to change the render mode but InteractiveServer is the only thing it will let me put after @rendermode

I thought about adding a scoped service that stores the username as a static string but that would only hold the username of the last logged in user.

Gemini has suggested building a state container that updates a username variable every time state change event occurs but I’m still worried that will have the same result as the scoped service thing I already tried.

2

Answers


  1. Chosen as BEST ANSWER

    I believe the issue came from misconfigured services in my program.cs file.

    builder.Services.AddAuthorization(options => {
        options.FallbackPolicy = options.DefaultPoicy;
    });
    builder.Services.AddCascadingAuthenticationState();
    

    seemed to be the missing pieces needed to get this to work.


  2. I’m not sure how you’re configuring your solution, so here’s a basic demo.

    Net8 – Server – Global Interactivity

    Project File:

    <Project Sdk="Microsoft.NET.Sdk.Web">
    
      <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
      </PropertyGroup>
    
        <ItemGroup>
            <PackageReference Include="Microsoft.AspNetCore.Authentication.Negotiate" Version="8.0.11" />
        </ItemGroup>
    
    </Project>
    

    Program, registering the necessary Authentication/Authorization services

    using BlazorApp.Components;
    using Microsoft.AspNetCore.Authentication.Negotiate;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddRazorComponents()
        .AddInteractiveServerComponents();
    
    builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
       .AddNegotiate();
    
    builder.Services.AddAuthorization(options =>
    {
        // By default, all incoming requests will be authorized according to the default policy.
        options.FallbackPolicy = options.DefaultPolicy;
    });
    
    var app = builder.Build();
    
    // Configure the HTTP request pipeline.
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/Error", createScopeForErrors: true);
        // 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.UseAntiforgery();
    
    app.UseAuthentication();
    app.UseAuthorization();
    
    app.MapRazorComponents<App>()
        .AddInteractiveServerRenderMode();
    
    app.Run();
    

    Routes, cascading the Authentication State.

    <CascadingAuthenticationState>
        <Router AppAssembly="@typeof(Program).Assembly">
            <Found Context="routeData">
                <RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
                <FocusOnNavigate RouteData="@routeData" Selector="h1" />
            </Found>
        </Router>
    </CascadingAuthenticationState>
    

    Home page, using the cascaded context to get the User.

    @page "/"
    
    <PageTitle>Home</PageTitle>
    
    <h1>Hello, world!</h1>
    
    <AuthorizeView>
    Hello, @context.User.Identity?.Name. Welcome to your new app.
    </AuthorizeView>
    

    The relevant code for AuthorizeView is:

        private AuthenticationState? currentAuthenticationState;
        private bool? isAuthorized;
    
        //.....
    
        [Parameter] public RenderFragment<AuthenticationState>? ChildContent { get; set; }
        [Parameter] public RenderFragment<AuthenticationState>? NotAuthorized { get; set; }
    
        protected override async Task OnParametersSetAsync()
        {
            if (ChildContent != null && Authorized != null)
                throw new InvalidOperationException($"Do not specify both '{nameof(Authorized)}' and '{nameof(ChildContent)}'.");
    
            if (AuthenticationState == null)
                throw new InvalidOperationException($"Authorization requires a cascading parameter of type Task<{nameof(AuthenticationState)}>. Consider using {typeof(CascadingAuthenticationState).Name} to supply this.");
    
            isAuthorized = null;
    
            currentAuthenticationState = await AuthenticationState;
            isAuthorized = await IsAuthorizedAsync(currentAuthenticationState.User);
        }
    
        protected override void BuildRenderTree(RenderTreeBuilder builder)
        {
            if (isAuthorized == null)
                builder.AddContent(0, Authorizing);
    
            else if (isAuthorized == true)
            {
                var authorized = Authorized ?? ChildContent;
                builder.AddContent(0, authorized?.Invoke(currentAuthenticationState!));
            }
    
            else
                builder.AddContent(0, NotAuthorized?.Invoke(currentAuthenticationState!));
        }
    
        //.... more code
    

    The complete code: https://github.com/dotnet/aspnetcore/blob/main/src/Components/Authorization/src/AuthorizeViewCore.cs

    The result:

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search