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();
Ritesh Singh
  • 181
  • 2
  • 12
Kevin
  • 457
  • 4
  • 12
  • 31
  • 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. – Kevin Jun 18 '21 at 20:12
  • I am the second person who has tried this and I get the same exact error. Did you fix this? – Garrett London Mar 30 '22 at 01: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. – Kevin Mar 31 '22 at 02: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. – Garrett London Mar 31 '22 at 02:44
  • This isn't a personal project, unfortunately, I have no other choice but to bang my head on the desk – Garrett London Mar 31 '22 at 02:44
  • Do you have a question out on your specific issue? Maybe trying to help you will bring me back into the fold. Maybe together we can figure it out – Kevin Apr 01 '22 at 03:07
  • Hey @Kevin I do have a question out here https://stackoverflow.com/questions/71686116/blazor-server-signalr-chat-works-on-local-not-on-azure. – Garrett London Apr 02 '22 at 05:06
  • Check out the answer below also, pretty interesting where the user and cookie is being passed to the hub, but the cookie domain has to be wrong it looks like, because the invalid json is still coming back – Garrett London Apr 02 '22 at 05:06

1 Answers1

1

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!

pbrotb
  • 11
  • 1
  • 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 – Garrett London Apr 02 '22 at 04:34
  • I'm on vacation until Wednesday, but when I get home I'll load up my project and try this out. Thank you! – Kevin Apr 03 '22 at 13:53
  • Thanks Kevin, please keep me updated! – Garrett London Apr 03 '22 at 18:35
  • 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. – pbrotb Apr 04 '22 at 07:10
  • That's what I tried but unfortunately still the same issue – Garrett London Apr 11 '22 at 01:03
  • For Azure B2C I had to manually create the cookie and manually update the headers. See: https://learn.microsoft.com/en-us/answers/questions/1160923/solution-custom-signalr-endpoints-(hubs)-in-a-blaz – Michael Washington Jan 16 '23 at 14:14
  • Thanks for this anwser was looking like crazy why SignalR auth with Blazor Server wasnt working – Niek Jannink Jun 02 '23 at 18:59