We recently built an ASP.NET Core API with Angular, and I'm attempting to implement a basic one-on-one chat functionality using SignalR. We're mapping User IDs to SignalR Connection IDs in a group, and then sending messages directly to users based on their User ID. 

It's working fine when tested with an MVC application, but when I try to use SignalR in my Angular project, it's giving me an exception: 'Failed to connect. Error during negotiation request.'

So in this post, we are going to discuss possible reasons for that error and provide solutions for the issue.

The error Angular SignalR "Failed to complete negotiation with the server" typically occurs due to issues with the negotiation between the client (Angular) and the server (SignalR). Let's discess Possible reasons for this:

If you are new to SignalR, you can read about it in this post. Here, I explain SignalR configuration and demonstrate how to use that SignalR hub with a JavaScript client in detail.

1.SignalR Hub Configuration: 

Ensure that the SignalR hub in your ASP.NET Core API Server is correctly configured and registered. 
Verify that it's set up to handle negotiation requests and establish connections with clients.Also, check if it is registered in the program.cs file or not.

Here is example of SignalR Hub Configuration


[AllowAnonymous]
    public class ChatHub : Hub
    {
        public override Task OnDisconnectedAsync(Exception exception)
        {
            Debug.WriteLine("Client disconnected: " + Context.ConnectionId);
            if (OnlineUsers.onlineUsers.Any(a => a.ConnectionId == Context.ConnectionId))
            {
                OnlineUsers.onlineUsers.RemoveAll(a => a.ConnectionId == Context.ConnectionId);
            }
            return base.OnDisconnectedAsync(exception);
        }
        public override Task OnConnectedAsync()
        {
            Debug.WriteLine("Client connected: " + Context.ConnectionId);
            return base.OnConnectedAsync();
        }
        //create for each user to chat sepearte
        public void SetUserChatGroup(string userChatId)
        {
            var id = Context.ConnectionId;
            Debug.WriteLine($"Client {id} added to group " + userChatId);
            if (OnlineUsers.onlineUsers == null)
                OnlineUsers.onlineUsers = new List<OnlineUserModel>();

            if (!OnlineUsers.onlineUsers.Any(a => a.ConnectionId == Context.ConnectionId))
            {
                OnlineUsers.onlineUsers.Add(new OnlineUserModel { ConnectionId = Context.ConnectionId, ChatId = userChatId });
            }
            Groups.AddToGroupAsync(Context.ConnectionId, userChatId);
        }
       
        //send message to user
        public async Task SendMessageToGroup(string senderChatId, string receiverChatId, string message)
        {
            var chatHistory = await new DbChatHistoryLog().InsertChatHistory(new ChatModel { SenderId = senderChatId, ReciverId = receiverChatId, Message = message });
            if (chatHistory != null && chatHistory.ChatId > 0 && Clients!=null)
            {
                await Clients.Group(receiverChatId).SendAsync("ReceiveMessage", chatHistory.SenderId, chatHistory.SenderName, chatHistory.ReciverId, chatHistory.Message);
            }
        }
        
    }

2.CORS (Cross-Origin Resource Sharing) Configuration: 

Verify that the CORS configuration on the server-side allows requests from the Angular application's domain. You may need to adjust the CORS policy to include the necessary origins, methods, and headers.

Check you Program.cs in case of .NET>6 and in Starup.cs in case .Net<=6 to ensure that the CORS configuration on the server allows requests from the Angular application's domain.

app.UseCors(builder => builder
    .AllowAnyHeader()
    .AllowAnyMethod()
    .SetIsOriginAllowed((host) => true)
    .AllowCredentials()
  );
Program.cs configuration example:
using SignalRDemo.Chat;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
builder.Services.AddSignalR();
builder.Services.AddSingleton<ChatingHub>();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

//Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseCors(builder => builder
    .AllowAnyHeader()
    .AllowAnyMethod()
    .SetIsOriginAllowed((host) => true)
    .AllowCredentials()
  );
app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();
app.MapHub<ChatingHub>("/ChatingHub");
app.Run();
Startup.cs configuration example:
public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
             
            services.AddOpenApiDocument(c =>
            {
                c.Title = "SignalR API";
            });
            services.AddCors(o => o.AddPolicy("AllowOrigin", builder =>
            {
                builder.AllowAnyOrigin()
                       .AllowAnyMethod()
                       .AllowAnyHeader();
            }));
            
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            services.AddSignalR();          
            services.AddSingleton<ChatingHub>();

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {


           

            app.UseStaticFiles(); // For the wwwroot folder
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            
            app.UseCors(builder => builder
                .AllowAnyHeader()
                .AllowAnyMethod()
                .SetIsOriginAllowed((host) => true)
                .AllowCredentials()
              );
            app.UseHttpsRedirection();
            // Make sure you call this before calling app.UseMvc()
            app.UseSignalR(routes =>
            {
                routes.MapHub<ChatingHub>("/ChatingHub");
            });
            app.UseMvc();
            //Register the Swagger generator      
            app.UseOpenApi();
            app.UseSwaggerUi3();
        }
    }
    

3.Authentication and Authorization: 

If your application uses authentication or authorization, ensure that SignalR negotiation requests are properly authenticated and authorized.If your SignalR URL is publicly available, then decorate your hub with the [AllowAnonymous] attribute. This will allow clients to connect to your hub.

4.Firewall or Network Restrictions: 

Also check if there are any firewall or network restrictions in you server where you hosted the SignalR server that might be blocking the negotiation requests between the client and server.
You can use browser developer tools to inspect the network requests and responses during the 
negotiation process between clinet and server and check specific errors or issues occurring during the negotiation.

5.Check Version : 

Make sure that the versions of SignalR used in both the server-side (ASP.NET Core API) and the client-side (Angular) are compatible with each other.
If there are compatibility issues between SignalR versions, consider updating both the server-side and client-side SignalR packages to compatible versions.

6.Make sure using correct Hub Url and try with skipNegotiation=true

skipNegotiation: true 
This option indicates whether to skip the negotiation process before establishing the connection. 
As we know thta negotiation is the process by which the client and server agree on a communication protocol. Setting skipNegotiation to true means that negotiation will be skipped, and the client assumes that the server supports the specified transport.

onnection = new signalR.HubConnectionBuilder()
    .configureLogging(signalR.LogLevel.Debug)
    .withUrl("http://localhost:4587/ChatHub", {
      skipNegotiation: true,
      transport: signalR.HttpTransportType.WebSockets
    })
    .build();
By following these solutions you should be able to resolve the "Failed to complete negotiation with the server" error in your Angular/SignalR application