3

I created a Blazor server app in an effort to start learning Blazor and some newer technologies. I followed along with a hub chat tutorial that Microsoft has. Things worked great. I then added some basic cookie authentication and things were still on the up and up. Then I followed a tutorial from Carl Franklin on Blazor Train about connecting an app to Azure AD B2C. It works, except for the chat hub portion of the app. All of the other portions of the app work fine and show the user's info.

I'm almost positive that the "hubconnection" is failing because it's not authenticating because it's not getting the access token. The failure happens on the await hubConnection.StartAsync(); line with the error System.Text.Json.JsonReaderException: '<' is an invalid start of a value. I'm pretty sure that HTML is coming back with a 403 message or something like that.

So I guess I technically have 2 questions:

  1. How can I view the value that's causing the HubConnectionBuilder to error out? Even if I put in breakpoints I can never see what the value is that's causing it to choke.

  2. How can I pass the access token to the HubConnectionBuilder?

I've found lots of "ways" to do this that are either outdated or I couldn't make work:

Uses AddAzureADB2CBearer which is deprecated

Passes parameters to App which I can't get to work

Is for Azure AD, not B2C

This is what was working with Cookie auth:

    hubConnection = new HubConnectionBuilder()
    .WithUrl(
        NavigationManager.ToAbsoluteUri("/chathub"),
                config => config.UseDefaultCredentials = true)
    .Build();

And now it seems like I need to pass in an access token based off of this Microsoft page about Auth and SignalR but

hubConnection = new HubConnectionBuilder()
    .WithUrl(
            NavigationManager.ToAbsoluteUri("/chathub"), options =>
            {
                options.AccessTokenProvider = () => Task.FromResult(_myAccessToken);
            })
    .Build();

Here is what I'm doing in Startup to get B2C working based on Carl Franklin YouTube video

//****Azure B2C****//
        services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAdB2C"));
        

        services.AddControllersWithViews()
                .AddMicrosoftIdentityUI();

        services.AddAuthorization(options =>
        {
            // By default, all incoming requests will be authorized according to the default policy
            options.FallbackPolicy = options.DefaultPolicy;
        });

        services.AddRazorPages();
        services.AddServerSideBlazor()
                .AddMicrosoftIdentityConsentHandler();
8
  • 1
    Am I the first person who's tried to use Azure AD B2C with Signalr and Blazor Server? Am I the only one crazy or dumb enough to try this? I thought there would at least be someone with something to say about this. Commented Jun 18, 2021 at 20:12
  • I am the second person who has tried this and I get the same exact error. Did you fix this? Commented Mar 30, 2022 at 1:24
  • @user2205904 sorry no. I mean yes I got it to work, but by removing the auth on the signal r portion. I looked around for weeks before simply giving up. It took all the wind out of my sails on my personal hobby project and I haven't used Blazor since. Commented Mar 31, 2022 at 2:33
  • Dude its about to do the same to me, how are there no chat applications written in blazor server that arent using singleton (cannot scope messages to users), or arent a broadcast.. Pains me so much. Commented Mar 31, 2022 at 2:44
  • This isn't a personal project, unfortunately, I have no other choice but to bang my head on the desk Commented Mar 31, 2022 at 2:44

1 Answer 1

2

I also had the same issue and managed to get it working. I had to manually pass through my authentication cookie to the Hub when connecting from my component.

This is my Hub Class:

[Authorize]
    public class NotificationsHub : Microsoft.AspNetCore.SignalR.Hub
    {
        public async override Task OnConnectedAsync() {
            var user = Context.User;
            var identifier = Context.UserIdentifier;
            string name = Context.User.Claims.First(x => x.Type.Equals("name")).Value;
            await base.OnConnectedAsync();
            await Clients.Caller.SendAsync("Connected", 0);
        }
    }

I then configured it in Program.cs

app.UseEndpoints(endpoints => {
    endpoints.MapControllers();
    endpoints.MapBlazorHub();
    endpoints.MapHub<NotificationsHub>("/notificationshub");
    endpoints.MapFallbackToPage("/_Host");
});

I also configured the Hub to use the UserId from my Microsoft cookie:

services.AddSingleton<IUserIdProvider, SignalRUserIdProvider>();
public class SignalRUserIdProvider : IUserIdProvider
    {
        public virtual string GetUserId(HubConnectionContext connection) {
            return connection.User?.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value!;
        }
    }

Finally, I passed through the cookie when connecting to my Hub. Example component:

@using Microsoft.AspNetCore.SignalR.Client
@using Microsoft.Extensions.Configuration
@implements IAsyncDisposable
@inject IHttpContextAccessor HttpContextAccessor;
@inject NavigationManager Navigate;

@code {
    private HubConnection hubConnection;

    protected override async Task OnInitializedAsync() {
        hubConnection = new HubConnectionBuilder()
            .WithUrl(Navigate.ToAbsoluteUri("/notificationshub"), options => {
                if (HttpContextAccessor.HttpContext != null) {
                    foreach (var cookie in HttpContextAccessor.HttpContext.Request.Cookies) {
                        // IF THE DOMAIN PARAMETER IS WRONG YOU WILL RECEIVE THE JSON ERROR.
                        options.Cookies.Add(new Cookie(cookie.Key, cookie.Value, null, domain: "localhost"));
                    }
                }
            })
            .WithAutomaticReconnect()
            .Build();

        hubConnection.On<int>("Connected", i => {
            // Yay, it worked
            StateHasChanged();
        });

        await hubConnection.StartAsync();
    }

    public async ValueTask DisposeAsync() {
        if (hubConnection is not null) {
            await hubConnection.DisposeAsync();
        }
    }
}

It is a long time since I had this issue, so please do let me know if anything is unclear.

Hope it helps!

Sign up to request clarification or add additional context in comments.

7 Comments

Hey, does the cookie have to be the domain of the Azure SignalR server or the domain of my azure app service? I have it working on local using your code and the user provider. Trying to figure out if HttpContext is null or its matching with the wrong domain. Going to look in logs
I'm on vacation until Wednesday, but when I get home I'll load up my project and try this out. Thank you!
Thanks Kevin, please keep me updated!
I'm not too sure - my SignalR server is part of my Blazor Server app so it's all running on a single site. The domain parameter for my production environment was simply the domain value of the cookie. In Chrome, when on your site, go to developer tools, then Application > Cookies, and use the Domain value for your .AspNetCore.Cookies cookie.
That's what I tried but unfortunately still the same issue
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.