dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.57k stars 10.05k forks source link

SignalR negotiate fails from Blazor Server with windows authentication #25000

Open bmoteria opened 4 years ago

bmoteria commented 4 years ago

Describe the bug

I have a Blazor Server app that uses Windows Authentication. It requires SignalR hub connection to update partial UI when a user click a button. I have followed Use ASP.NET Core SignalR with Blazor WebAssembly documentation as well read #22767 and tried following SignalR cross-origin negotiation for authentication. The following code works when debugging locally on IIS Express using Visual Studio 2019 Enterprise but it returns 401 Unauthorized exception while starting hub connection when it's deployed to IIS, especially when it has subdomain binding (http://subdomain.mydomain.org). It only works when navigating via server name and port number "http://servername:60006". Please note that the signalr hub are hosted along with Blazor server app so there is no cross origin.

NavMenu.razor

@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager NavigationManager
@implements IDisposable

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">BlazorSignalRDemo</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter (@counter)
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </li>
    </ul>
</div>

@code {
    private string hubErrorMsg;
    private HubConnection hubConnection;
    private int counter = 0;
    private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }

    protected override async Task OnInitializedAsync()
    {
        hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/taskhub"), config =>
            {
                config.UseDefaultCredentials = true;
                config.HttpMessageHandlerFactory = innerHandler => new IncludeRequestCredentialsMessageHandler {
                     InnerHandler = innerHandler
                };
            })
            .WithAutomaticReconnect()
            .Build();

        hubConnection.On<int>("ReceiveTaskCount", (counter) =>
        {
            this.counter = counter;
            StateHasChanged();
        });

        try
        {
            await hubConnection.StartAsync();
        }
        catch (Exception ex)
        {
            hubErrorMsg = ex.Message;
        }
    }

    public void Dispose()
    {
        _ = hubConnection.DisposeAsync();
    }
}

IncludeRequestCredentialsMessageHandler.cs

using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace BlazorSignalRDemo
{
    public class IncludeRequestCredentialsMessageHandler : DelegatingHandler
    {
        public IncludeRequestCredentialsMessageHandler()
        {
            InnerHandler = new HttpClientHandler()
            {
                Credentials = CredentialCache.DefaultNetworkCredentials,
                UseDefaultCredentials = true,
                PreAuthenticate = true
            };
        }

        protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
        {
            // The following SetBrowserRequestCredentials(...) api is not available for Blazor Server Side.
            //request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);

            return base.SendAsync(request, cancellationToken);
        }
    }
}

To Reproduce

Exceptions (if any)

System.Net.Http.HttpRequestException: Response status code does not indicate success: 401 (Unauthorized).
   at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.NegotiateAsync(Uri url, HttpClient httpClient, ILogger logger, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.GetNegotiationResponseAsync(Uri uri, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.SelectAndStartTransport(TransferFormat transferFormat, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.StartAsyncCore(TransferFormat transferFormat, CancellationToken cancellationToken)
   at System.Threading.Tasks.ForceAsyncAwaiter.GetResult()
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.StartAsync(TransferFormat transferFormat, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionFactory.ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionFactory.ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncCore(CancellationToken cancellationToken)
   at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncInner(CancellationToken cancellationToken)
   at System.Threading.Tasks.ForceAsyncAwaiter.GetResult()
   at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsync(CancellationToken cancellationToken)
   at BlazorSignalRDemo.Shared.NavbarItemsBase.OnInitializedAsync() in C:\<MY_PATH>\BlazorSignalRDemo\Shared\NavbarItems.razor.cs:line 38
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)
   at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessAsynchronousWork()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderRootComponentAsync(Int32 componentId, ParameterView initialParameters)
   at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.CreateInitialRenderAsync(Type componentType, ParameterView initialParameters)
   at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.RenderComponentAsync(Type componentType, ParameterView initialParameters)
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.<>c__11`1.<<InvokeAsync>b__11_0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.ViewFeatures.StaticComponentRenderer.PrerenderComponentAsync(ParameterView parameters, HttpContext httpContext, Type componentType)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.RenderComponentAsync(ViewContext viewContext, Type componentType, RenderMode renderMode, Object parameters)
   at Microsoft.AspNetCore.Mvc.TagHelpers.ComponentTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
   at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.<RunAsync>g__Awaited|0_0(Task task, TagHelperExecutionContext executionContext, Int32 i, Int32 count)
   at BlazorSignalR.Pages.Pages__Host.<ExecuteAsync>b__17_1() in C:\<MY_PATH>\BlazorSignalR\Pages\_Host.cshtml:line 23
   at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync()
   at BlazorSignalR.Pages.Pages__Host.ExecuteAsync() in C:\<MY_PATH>\BlazorSignalR\Pages\_Host.cshtml:line 5
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeResultFilters>g__Awaited|27_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Builder.Extensions.MapMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Further technical details

Runtime Environment: OS Name: Windows OS Version: 10.0.18363 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\3.1.401\

Host (useful for support): Version: 3.1.7 Commit: fcfdef8d6b

.NET Core SDKs installed: 3.1.400 [C:\Program Files\dotnet\sdk] 3.1.401 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed: Microsoft.AspNetCore.All 2.1.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.21 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.App 2.1.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.21 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 2.1.20 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.21 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 3.1.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 3.1.7 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

To install additional .NET Core runtimes or SDKs: https://aka.ms/dotnet-download


- The IDE (VS / VS Code/ VS4Mac) you're running on, and it's version
      1. VS Enterprise 2019 v16.8.0 Preview 1.0
      2. VS Enterprise 2019 v16.7.1

Thanks,
Bishan M.
javiercn commented 4 years ago

@bmoteria thanks for contacting us.

Are you trying to start a blazor server application cross-origin?

bmoteria commented 4 years ago

@javiercn No. The blazor server app and signalr hub are hosted on the same site. Here is the sample project.

ghost commented 4 years ago

Thanks for contacting us. We're moving this issue to the Next sprint planning milestone for future evaluation / consideration. We will evaluate the request when we are planning the work for the next milestone. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

bmoteria commented 4 years ago

@captainsafia Is there any workaround for this issue? We're writing an enterprise application and we need to determine if could proceed further or find an alternative path. Thanks! (cc @BrennanConroy)

captainsafia commented 4 years ago

Hi @bmoteria -- not sure yet! We'll be investigating this issue soon and will post back about whether this a bug/known issue/fixable in your app/etc.

ghost commented 4 years ago

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

JorgeJMartins commented 4 years ago

This is a blocking issue for me too. The only way I got rid of the 401 was to allow Anonymous Authentication on IIS on a QA environment but that is not an option for Production.

faarbaek commented 4 years ago

I also get a 401, but this is on Azure App Services using Azure AD. It works using the subdomain.azurewebsites.net domain but when I access the site from our custom configured domain I get a 401. I have added CORS to allow all origins but still no luck. Is there any workaround for this issue?

raltrifork commented 3 years ago

I am facing this exact same issue, in an angularjs client. I can get passed it by using websockets as transport only, and then setting skipNegotiation to false. Then it works for obvious reasons.

JorgeJMartins commented 3 years ago

I am facing this exact same issue, in an angularjs client. I can get passed it by using websockets as transport only, and then setting skipNegotiation to false. Then it works for obvious reasons.

@raltrifork, thanks for the tip. Does this makes sense?

    _hubConnection = new HubConnectionBuilder()
    .WithUrl(_hubUrl, options =>
    {
        options.UseDefaultCredentials = true;
        options.SkipNegotiation = false;
        options.Transports = Microsoft.AspNetCore.Http.Connections.HttpTransportType.WebSockets;
    })
    .WithAutomaticReconnect()
    .Build();

Still no luck. Sticky 401! :-( I also tried some other options ... same result!

bmoteria commented 3 years ago

We found a temporary workaround. Instead of using signalr, we now use Scoped service (for example TaskCountService.cs). The code inside task count service looks like so:

public event Action OnReloadTaskCount;

public void ReloadTaskCount()
{
    OnReloadTaskCount?.Invoke();
}

Subscribe to OnReloadTaskCount event inside navbar component (TopNavbar.razor):

public partial class TopNavbar
{
    [Inject]
    protected TaskService TaskService { get; set; }
    [Inject]
    protected TaskCountService TaskCountService { get; set; }

    protected overriede void OnInitialized()
    {
        TaskCountService.OnReloadTaskCount += ReloadTaskCount;
    }

    private void ReloadTaskCount()
    {
        InvokeAsync(() => {
            _assignedToMeCount = TaskService.GetAssignedToMeCount(_currentUserIdentityName);
            StateHasChanged();
        });
    }
}

Now that it's subscribed to an event, you can invoke ReloadTaskCount method of TaskCountService.cs from any razor pages/components:

*****Create.razor.cs*****
public partial class Create
{
    [Inject]
    protected TaskCountService TaskCountService { get; set; }
    // other code goes here...

    public async Task SubmitValidFormAsync()
    {
        // calling database repository.
        // and other stuff..
        TaskCountService.ReloadTaskCount();
    }
}
raltrifork commented 3 years ago

Jesus! My mistake I meant setting SkipNegotiation to true, if this is set it should at least not be on the negotiation request the exception is thrown, since it is not performed.

JorgeJMartins commented 3 years ago

Just another follow up to say I found another way to make it work (temporarily, until it's solved by the SignalR/Core/Blazor team) and it's by moving the Hub to a separate project, publish it on a separate IIS application and with anonymous authentication enabled. Had to move also the WEB API Controller that interacts with the hub and, for the moment, it also has to remain with anonymous authentication. This way I've been able to remove the anonymous authentication from the main project. So far it's working fine, not graceful, just fine.

Amangeldi commented 3 years ago

I have the same problem. Are you going to fix this?

darrylluther commented 3 years ago

I was able to resolve this issue by simply setting the UseDefaultCredentials configuration option to true:

this.hubConnection = new HubConnectionBuilder()
                     .WithUrl(
                              hubUrl,
                              config => config.UseDefaultCredentials = true)
                     .WithAutomaticReconnect()
                     .Build();
RikDriever commented 3 years ago

My workaround for this issue is to catch the SignalR update with JavaScript and invoke a Blazor method.

JavaScript code:

const connection = new signalR.HubConnectionBuilder().withUrl("/myhub").build();

window.blazorSignalR = {
    init: function (dotnetHelper, bindTo, nameFunc) {
        connection.on(bindTo,
            function (someArg) {
                return dotnetHelper.invokeMethodAsync(nameFunc, someArg);
            });
    }
}

Component code:

private DotNetObjectReference<myComponent> dotNetRef;
private string EventName = "SomethingHappened";

protected override async Task OnInitializedAsync()
{
   dotNetRef = DotNetObjectReference.Create(this);
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    await base.OnAfterRenderAsync(firstRender);

    if (firstRender)
    {
        await JSRuntime.InvokeVoidAsync("blazorSignalR.init", dotNetRef, EventName, "OnSomethingHappened");
    }
}

[JSInvokable("OnSomethingHappened")]
public async Task OnSomethingHappened(string data)
{
   // React to SomethingHappened
}

SignalR: await myHub.Clients.All.SendAsync("SomethingHappened", "data");

Haraldi-Coding commented 2 years ago

I was able to resolve this issue by simply setting the UseDefaultCredentials configuration option to true:

this.hubConnection = new HubConnectionBuilder()
                     .WithUrl(
                              hubUrl,
                              config => config.UseDefaultCredentials = true)
                     .WithAutomaticReconnect()
                     .Build();

Thank you so much, that did the trick!

Addendum: No, only when running locally, but when published to IIS I get the error 401 (unauthorized) again.

Any updates on that?

blindmeis commented 2 years ago

Any updates? just work local but no on real IIS

szczz commented 2 years ago

We're also waiting for an update on this.

darrylluther commented 2 years ago

Almost 2 years and this is still awaiting a response? @captainsafia Any updates?

chinmay-trivedi commented 2 years ago

Any update on this ? We need this feature as most of our apps uses Windows Authentication and not able to use Blazor Server App with SignalR.

@darrylluther Thanks for the trick, works locally, but not working with IIS after deployment.

Ackermannen commented 1 year ago

Any update on this thread. This still does not work for me as i still get 401 Unauthorized when using Windows Authentication

deepdarkenergy commented 1 year ago

Same error here. Waiting for updates

liamcannon commented 1 year ago

Ran into the same problem. Works fine locally, 401 when deployed to iis.

ghost commented 11 months ago

To learn more about what this message means, what to expect next, and how this issue will be handled you can read our Triage Process document. We're moving this issue to the .NET 9 Planning milestone for future evaluation / consideration. Because it's not immediately obvious what is causing this behavior, we would like to keep this around to collect more feedback, which can later help us determine how to handle this. We will re-evaluate this issue, during our next planning meeting(s). If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact work.

mkArtakMSFT commented 11 months ago

After further discussion we believe this is not specific to Blazor by any means and is rather something that should be addressed on the SignalR side. @BrennanConroy moving to your area path.

BrennanConroy commented 11 months ago

After further discussion

Can you share any details about this discussion...

Ackermannen commented 11 months ago

Update on our side, we managed to get it to work, but not through using SignalR, we had to revert to using long polling instead. When connecting using HubConnectionBuilder, configure the transport to use websockets, then longpolling. Do the same for MapHub in Program.cs. This is not good for production scenarios, but fine for us with <10 users.

Hope this helps anyone else.

halter73 commented 11 months ago

Are people in this thread trying to do impersonation? Or are you just trying to authenticate with the application's identity?

If you're trying to authenticate with the apps identity, setting UseDefaultCredentials as suggested in https://github.com/dotnet/aspnetcore/issues/25000#issuecomment-795685660 should work. I'm guessing all the thumbs up means this has worked for a bunch of people.

If you're trying to do impersonation, take a look at the impersonation docs at https://learn.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth?view=aspnetcore-8.0&tabs=visual-studio#impersonation

szczz commented 11 months ago

@halter73

Only works locally. Not when the app is deployed to IIS.

halter73 commented 11 months ago

That didn't answer my question, but I'm going to assume that means you're not trying to do impersonation. In that case, it probably means that app pool identity does not have permission to access the SignalR server. This would make sense without some custom configuration considering that the default virtual account created by IIS is not joined to any domain.

On IIS 8.0 or later, the IIS Admin Worker Process (WAS) creates a virtual account with the name of the new app pool and runs the app pool's worker processes under this account by default.

https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/advanced?view=aspnetcore-8.0#application-pool-identity

BrennanConroy commented 10 months ago

If you're trying to authenticate with the apps identity, setting UseDefaultCredentials as suggested in #25000 (comment) should work. I'm guessing all the thumbs up means this has worked for a bunch of people.

If you're trying to do impersonation, take a look at the impersonation docs at https://learn.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth?view=aspnetcore-8.0&tabs=visual-studio#impersonation

To be clearer here I'll give code examples and screenshots. It seems most people are writing code like:

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
<h2>@name</h2>

@code
{
    private string name;
    private int currentCount = 0;
    private HubConnection? hubConnection;

    protected override async Task OnInitializedAsync()
    {
        hubConnection = new HubConnectionBuilder()
        .WithUrl(NavigationManager.ToAbsoluteUri("/hub"), config =>
        {
            config.UseDefaultCredentials = true;
        })
        .WithAutomaticReconnect()
        .Build();

        hubConnection.On<string>("name", userName =>
        {
            name = userName;
            InvokeAsync(StateHasChanged);
        });
        await hubConnection.StartAsync();
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}

And this works for IISExpress because IISExpress is running as the logged in user. Which during development is likely your personal/work account. But when you publish the app to IIS, the app is now running under the Application Pool Identity. This means the HubConnection isn't going to connect as the user that is accessing the page, but as the "user" hosting the app.

image

What most people probably want is for the HubConnection to connect as the user that is accessing the application. This is where impersonation comes in. You need to tell the application to run the HubConnection code as the browsing user.

@using Microsoft.AspNetCore.Components.Authorization
@inject AuthenticationStateProvider AuthenticationStateProvider

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
<h2>@name</h2>

@code {
    private int currentCount = 0;
    private HubConnection? hubConnection;
    private string name;

    protected override async Task OnInitializedAsync()
    {
        var user = (WindowsIdentity)(await AuthenticationStateProvider.GetAuthenticationStateAsync()).User.Identity;

        await WindowsIdentity.RunImpersonatedAsync(user.AccessToken, async () =>
        {
            hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/hub"), config =>
            {
                config.UseDefaultCredentials = true;
            })
            .WithAutomaticReconnect()
            .Build();

            hubConnection.On<string>("name", userName =>
            {
                name = userName;
                InvokeAsync(StateHasChanged);
            });
            await hubConnection.StartAsync();
        }
        );
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
image
mkArtakMSFT commented 10 months ago

@BrennanConroy do you think this should be documented or is there any other pending work here? We still are somewhat unsure how this related to Blazor Server.

BrennanConroy commented 10 months ago

do you think this should be documented or is there any other pending work here?

Yes, this is clearly a Blazor Server coding pattern issue that people don't understand. We should document it, or if you're feeling crazy, Blazor Server can wrap component code in WindowsIdentity.RunImpersonatedAsync(...) when Windows Auth is being used so users don't have to think about it when writing their components.

DitherDude commented 9 months ago

to all those using NginX, this is the X/Y problem - the issue lies within your nginx configuration! search for the line statring with proxy_set_header Connection and replace it with proxy_set_header Connection "upgrade"; (see this StackOverflow answer) - for me i had to replace proxy_set_header Connection keep-alive; with the above.

QuinnNash commented 4 months ago

`@using Microsoft.AspNetCore.Components.Authorization @inject AuthenticationStateProvider AuthenticationStateProvider @using System.Security.Principal

@using Microsoft.AspNetCore.SignalR.Client @using BlazorBootstrap

@inject NavigationManager Navigation @implements IAsyncDisposable

@test @if (progress == 100) { Download Finished }
@progress %

@code { private HubConnection? _hubConnection; private int progress; private string test;

protected override async Task OnInitializedAsync()
{

    var user = (WindowsIdentity)(await AuthenticationStateProvider.GetAuthenticationStateAsync()).User.Identity;
    test = user.Name;

    await WindowsIdentity.RunImpersonatedAsync(user.AccessToken, async () =>
    {
        _hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"), config =>
        {
            config.UseDefaultCredentials = true;
        })
        .WithAutomaticReconnect()
        .Build();

        _hubConnection.On<int>("ReceiveProgress", p =>
        {
            progress = p;
            InvokeAsync(StateHasChanged);
        });

        await _hubConnection.StartAsync();
    }
    );

}

public async ValueTask DisposeAsync()
{
    if (_hubConnection != null)
    {
        await _hubConnection.DisposeAsync();
    }
}

}`

still fails on the server despite having a valid username image

System.Net.Http.HttpRequestException: Response status code does not indicate success: 401 (Unauthorized). at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode() at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.NegotiateAsync(Uri url, HttpClient httpClient, ILogger logger, CancellationToken cancellationToken) at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.GetNegotiationResponseAsync(Uri uri, CancellationToken cancellationToken) at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.SelectAndStartTransport(TransferFormat transferFormat, CancellationToken cancellationToken) at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.StartAsyncCore(TransferFormat transferFormat, CancellationToken cancellationToken) at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.StartAsync(TransferFormat transferFormat, CancellationToken cancellationToken) at Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionFactory.ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken) at Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionFactory.ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken) at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncCore(CancellationToken cancellationToken) at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncInner(CancellationToken cancellationToken) at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsync(CancellationToken cancellationToken)

works on local but but not iis server with windows auth enabled and websockets enabled

Local host content image

QuinnNash commented 4 months ago

@BrennanConroy

scarybook commented 2 months ago

@QuinnNash

If you want to fix the 401 error code you need to have trusted SSL certificate for hosted application.

When you are trying to deploy app on IIS be sure that you created valid SSL certificate for the host (IIS Development Certificate or any created by Visual Studio or by you with PowerShell) - so if you deploy app with binding for https as "localhost" create certificate for localhost, if you deploy as "app.test" create certificate for "app.test", same for IP address.

Next you have to copy or move created certificate to Trusted Root Certification Authorities (you can do it in certmgr.msc).

I had the same issue and that helped me in development environment.

DitherDude commented 2 months ago

My Blazor program is running on an rpi with Lets Encrypt SSL security (hosted on https:// with a lock).

When page is accessed without refresh, console reports only the following error Failed to load resource: net::ERR_BLOCKED_BY_CLIENT - and works fine. When i refresh the page, i get a .NET error with 123:1 GET https://{my site} 500 (Internal Server Error) error in the console.